presentPaywall
Open a screen over your page and get a promise that resolves when the visitor buys, restores, closes, acts or is skipped.
presentPaywall opens a screen over your page. It resolves when the visitor finishes with it. Use it for upgrade buttons, feature gates, prompts and any moment your own code decides.
function presentPaywall(options: PresentOptions): Promise<PurchaseResult>;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',
offer: { price: 0, period: { unit: 'day', count: 7 } },
},
},
onPurchase: async ({ packageId }) => startCheckout(packageId),
});Options
PresentOptions extends PaywallOptions. Every option is optional except paywall.
Where the screen comes from
| Option | Type | Default | What it does |
|---|---|---|---|
paywall | PaywallSource | required | A placement { placement: 'key' }, a published screen { paywallId: 'id' }, a URL string that returns the JSON, or the JSON object. See loadPaywall. |
screens | an object of screen ID to JSON object or URL string | none | For JSON and URL sources only: the screens that a "Go to another screen" button can open, by screen ID. Screens loaded by placement or paywall ID fetch linked screens from Miniwall instead. |
How it looks
| Option | Type | Default | What it does |
|---|---|---|---|
mode | 'modal', 'fullscreen', 'banner', 'slide_in', 'toast', 'drawer', 'launcher' | the placement's format, else 'modal' | Format. When you pass it, the placement's format, position, size and duration are ignored. |
position | 'top', 'bottom', 'bottom_right', 'bottom_left', 'bottom_center', 'corner', 'left', 'right' | the placement's, else 'top' for a banner, 'bottom_right' for a slide-in, 'bottom' for a toast, 'right' for a drawer | Where a banner, slide-in, toast or drawer sits. |
size | 'small', 'medium', 'large' | the placement's, else 'medium' | Width of a modal, slide-in, toast or drawer. |
duration | number | the placement's, else 6 | Seconds a toast stays (3 to 60). |
closeOnBackdrop | boolean | true | Close a modal or drawer when the dimmed area is clicked. |
maxContentWidth | string | none | Maximum width of the content, for example '480px'. For a modal it replaces the width from size. |
colorMode | 'light', 'dark', 'system' | from configure(), else 'light' | Colour scheme. |
locale | string | from configure(), else the screen's default | Language, such as vi_VN (vi-VN also works). If the screen has no such language, a language with the same first part is used, then the screen's default. |
See Display modes for each format.
Prices and text
| Option | Type | Default | What it does |
|---|---|---|---|
products | an object of package ID to ProductInfo | none | A price for each package ID. Fills every {{ product.* }} text. See Purchases and prices. |
variablesPerPackage | an object of package ID to an object of text values | none | Extra {{ product.* }} values per package. They override the computed ones. |
customVariables | an object whose values are strings, numbers or booleans | none | Values for {{ custom.name }} in the screen's text. Shown as plain text, never as HTML. |
Callbacks
| Option | Called when | Return value |
|---|---|---|
onPurchase({ packageId, product, answers }) | The visitor taps a purchase button. | true or nothing: purchased. false: cancelled, the screen stays open. Throw: failure, the screen stays open and onError is called. |
onRestore() | The visitor taps Restore purchases. The button only appears when you pass this. | true or nothing: restored. false: nothing restored, the screen stays open. |
onAction({ actionId, answers }) | A "Run your code" button is pressed. | false keeps a presented screen open. Anything else closes it. |
onAnswer({ fieldId, value, answers }) | The visitor changes an answer. | none |
onClose() | The visitor closes the screen. | none |
onSkip({ reason }) | A placement's targeting chose to show nothing. | none |
onOpenUrl(url) | A link in the screen is pressed. Default: open in a new tab. | none |
onError(error) | Something failed after the screen opened. | none |
Full details are on Callbacks.
Targeting
| Option | Type | What it does |
|---|---|---|
attributes | Attributes | What you know about the visitor, merged over configure({ attributes }) for this call. Used by the placement's audiences. Never sent to Miniwall. See Targeting attributes. |
The result
type PurchaseResult =
| { outcome: 'purchased'; packageId: string; answers?: Answers }
| { outcome: 'restored'; answers?: Answers }
| { outcome: 'closed'; answers?: Answers }
| { outcome: 'action'; actionId: string; answers?: Answers }
| { outcome: 'skipped'; reason: 'frequency_cap' | 'audience' | 'holdout' };answers is present when the visitor answered at least one question. See Answers.
Use result.outcome in a switch. skipped means nothing opened. The promise rejects (it does not resolve) when the screen cannot be loaded, for example when the key is wrong or the network failed with no saved copy. Wrap the call in try/catch. See Errors.
try {
const result = await presentPaywall({ paywall: { placement: 'upgrade_button' }, products, onPurchase });
switch (result.outcome) {
case 'purchased': unlockPro(result.packageId); break;
case 'action': handleAction(result.actionId, result.answers); break;
case 'skipped': break; // the placement chose not to show
default: break; // closed or restored
}
} catch (error) {
console.error('Could not show the paywall', error);
}What happens while it is open
- Only one screen is presented at a time. Presenting another closes the current one, and the earlier promise resolves with
closed. - While your
onPurchase,onRestoreoronActionruns, the screen shows a busy spinner and ignores further taps, so a double tap cannot start two checkouts. - A screen with "Go to another screen" buttons moves between screens inside the same frame. The promise resolves once, when the flow ends. See Answers.
- Esc closes the screen. For a banner, slide-in or toast it closes only when focus is inside it, so it does not interrupt the visitor.
- For popups, full screens and drawers the page behind is locked from scrolling, focus stays inside the screen (Tab and Shift+Tab wrap around), and focus returns to where it was when the screen closes.
- The SDK adds no close button. A screen closes with a button in its own design that uses the action Close the screen, with Esc, with a click on a modal's backdrop, or, for a toast, by itself.
- Links in the screen open in a new tab unless you handle
onOpenUrl. Onlyhttp,https,mailto,teland links that start with a single/are ever opened.
With a placement
When paywall is { placement } the SDK also applies the placement's setup in the dashboard: which screen, its A/B test, a schedule in force, audiences, the frequency limit, the holdout and the display format. See Placements.
Views, closes, purchase taps, purchases, restores, button presses and chosen answers are reported to your Analytics unless you set analytics: false. See Events and privacy.