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
monthlyorannual. - 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.
| Part | What it does |
|---|---|
| ID field | Type the ID. Letters, numbers and the characters _ $ . -, up to 64 characters. |
| Suggested IDs | One-click chips: weekly, monthly, annual, lifetime, plus any ID already used on this screen. |
| Save ID | Saves 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:
| Setting | What it does |
|---|---|
| Package → ID button | The package ID, above. |
| Selected by Default | Whether this card starts selected. Turn it on for one card only. |
| Style sections | The card is a frame, so it has the same layout, size, fill, border and shadow settings as a Stack. |
| Default and When selected tabs | Switch 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.
| ID | Example price |
|---|---|
weekly | 2.99 USD per week |
monthly | 9.99 USD per month |
annual or yearly | 59.99 USD per year |
lifetime | 149 USD, once |
credits_100 | 4.99 USD, once, named "100 credits" |
credits_500 | 19.99 USD, once, named "500 credits" |
team | 24 USD per month |
| Any other ID | The 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 field | What it is |
|---|---|
price | The price in major units: 9.99, not 999. |
currency | A currency code such as USD. |
period | day, week, month, year, lifetime, or an object such as { unit: 'month', count: 3 }. |
title | Optional product name, shown by {{ product.store_product_name }}. |
offer | Optional 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 })buildsproductsfrom 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,
});