Miniwall Docs
Visual editor

Packages and pricing

Build plan cards, give each one an ID, and see how your site supplies the real prices and runs checkout.

Use this page when you build a paywall: plan cards the visitor can pick, a button that buys, and prices that stay correct without changing the screen.

How pricing works

Miniwall shows your screen. It does not charge anyone, and it does not hold your prices.

  • In the editor you build plan cards and give each card an ID you choose, such as monthly or annual.
  • On your site, you pass the price of each plan under the same ID.
  • When the visitor presses the Purchase Button, Miniwall tells your code which ID was selected. Your code runs your own checkout (Stripe, Paddle, your backend).

That makes the ID the contract between the screen and your code. Change a price in your store and the screen follows, without a new publish.

Add plan cards

Open Components, then under Purchase, add Package. The editor adds a Packages stack with two cards: Monthly and Annual. Annual is selected by default. Both start with a placeholder ID.

Select a card. In the Design panel under Package, click the ID button. It reads Set package ID until you choose one.

Add a Purchase button from the same group. Its default label is "Purchase".

You can add, duplicate, move and delete cards like any layer. A duplicated card gets a fresh ID automatically: monthly becomes monthly_2, and the copy is not selected by default.

The package ID

Click the ID button on a card to open the Package ID dialog.

PartWhat it does
ID fieldType the ID. Letters, numbers and the characters _ $ . -, up to 64 characters.
Suggested IDsOne-click chips: weekly, monthly, annual, lifetime, plus any ID already used on this screen.
Save IDSaves the ID. It stays disabled while the ID is invalid.

If the ID breaks the rules you see "Use letters, numbers, _ $ . - (up to 64 characters)."

Each card needs its own ID. If you type one that another card on this screen already uses, the dialog warns: "Another package in this paywall uses ... Saving moves the ID here and clears it there." Saving then moves the ID to this card and empties the other card's ID. You can use this to swap the IDs of two cards.

An ID is the same string your site uses as a key, so it is case sensitive. Pick short, stable names and keep them when you change prices.

Card settings

With a card selected, the Design panel shows:

SettingWhat it does
Package → ID buttonThe package ID, above.
Selected by DefaultWhether this card starts selected. Turn it on for one card only.
Style sectionsThe card is a frame, so it has the same layout, size, fill, border and shadow settings as a Stack.
Default and When selected tabsSwitch between the normal look and the look when the card is picked. For example, a thicker colored border for the selected card.

Layers inside a card (text, icons, images) have the same Default and When selected tabs, so a price can turn bold when its card is picked.

When the screen opens, the selected card is the one marked Selected by Default. If none is marked, it is the first card on the screen.

The visitor picks a card by pressing it. A card holds no buttons or other tappable layers, because the whole card is the button.

The Purchase Button

The Purchase Button is a frame with a label, like a button. Under Checkout Behavior, Type has one option: In-App Checkout. Pressing it calls your onPurchase with the package that is selected right now. Style it like any frame.

If the visitor has no card selected, pressing the button does nothing.

A screen that has plan cards needs a purchase button, and a screen with a purchase button needs at least one plan card. Screens without plans, such as onboarding and announcements, need neither. See Validation.

Prices in the editor

On the canvas, every card shows example prices so you can judge the layout. The numbers are not yours and are not saved.

IDExample price
weekly2.99 USD per week
monthly9.99 USD per month
annual or yearly59.99 USD per year
lifetime149 USD, once
credits_1004.99 USD, once, named "100 credits"
credits_50019.99 USD, once, named "500 credits"
team24 USD per month
Any other IDThe monthly example

IDs are matched without regard to upper or lower case. Text inside a card shows that card's own price. Text outside the cards, such as a line under the button, shows the price of the card that is selected.

Prices on your site

Pass products to the Web SDK, keyed by package ID.

import { configure, presentPaywall } from '@ui-kit/web';

configure({ apiKey: 'pk_...' });

const result = await presentPaywall({
  paywall: { placement: 'upgrade_button' },
  products: {
    monthly: { price: 9.99, currency: 'USD', period: 'month' },
    annual: {
      price: 59.99,
      currency: 'USD',
      period: 'year',
      // A 7-day free trial: an offer with price 0.
      offer: { price: 0, period: { unit: 'day', count: 7 } },
    },
  },
  onPurchase: async ({ packageId }) => {
    // Run your own checkout. Return true when paid, false if cancelled.
    return startCheckout(packageId);
  },
});
products fieldWhat it is
priceThe price in major units: 9.99, not 999.
currencyA currency code such as USD.
periodday, week, month, year, lifetime, or an object such as { unit: 'month', count: 3 }.
titleOptional product name, shown by {{ product.store_product_name }}.
offerOptional intro or trial offer: { price, period }. A free trial has price 0.

The SDK turns each entry into every {{ product.* }} variable your texts use, formatted for the visitor's locale. See Variables.

Two helpers save you work:

  • getPackageIds(paywallJson) lists the IDs a screen needs.
  • productsFromStripe({ monthly: price1, annual: price2 }) builds products from Stripe Price objects that your server fetched.

If the screen shows a package that has no price in products, the SDK writes a warning to the browser console: "No price for package(s) ... Pass them in products (keys are the package IDs set in the editor)." Texts that use price variables for that package then show nothing.

If checkout sends the visitor to another page and back, call reportPurchase({ packageId }) on your success page so the purchase counts for the screen where they pressed buy. See Purchases.

Passing prices when you cannot know them in advance

Use variablesPerPackage for any value the SDK does not compute from products. It adds to, and overrides, the computed values for one package:

presentPaywall({
  paywall: { placement: 'upgrade_button' },
  products,
  variablesPerPackage: {
    annual: { 'product.offer_end_date': 'March 31, 2027' },
  },
  onPurchase,
});

On this page