Present a screen
Open a screen over your page or app and get a result when the user buys, restores, closes, acts or is skipped.
present opens a screen over your page or app and finishes when the user is done 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),
});Where the screen comes from
| Source | Loads | Analytics |
|---|---|---|
Placement (web { placement }, iOS .placement, Android ScreenSource.Placement) | The screen the placement shows now: its A/B test, schedule, audiences, frequency limit, holdout and display format apply. Recommended. Needs the API key. | Yes |
Paywall ID ({ paywallId }, .paywallId, ScreenSource.PaywallId) | That screen's published version. Needs the API key. | Yes |
JSON (.json, ScreenSource.Json, { json }; on web the JSON object itself) | A screen JSON you bundle, such as a file from Download JSON on the Screens page. | None |
| URL (web only) | JSON from a URL string. | None |
See Preload and sources for the JSON format and caching. A JSON source without components_config.base fails: Miniwall: not a screen JSON (expected components_config.base.stack) on mobile, Not a paywall JSON: expected components_config.base on web.
Options
Every option is optional (on web, paywall is required).
Source and look
| Option | Type | Default | What it does |
|---|---|---|---|
paywall | source | required | Web only: the source (placement, paywall ID, URL or JSON). On mobile the source is the first argument. |
mode (web) / style (mobile) | see Display modes | the placement's format, else web modal, mobile full screen | Format. When you pass it, the placement's format (and on web its position, size and duration) is ignored. |
position, size, duration, closeOnBackdrop, maxContentWidth | see Display modes | Web only. | |
colorMode | light, dark, system | from configure, else web light, mobile system | Colour scheme. |
locale | string | from configure, else the screen's default (web) or the device's (mobile) | Language, such as vi_VN (vi-VN also works). If the screen lacks it, a language with the same first part is used, then the screen's default. |
screens | map of screen ID to JSON (web: or URL) | none | JSON and URL sources only: the screens a "Go to another screen" button can open. Placement and paywall ID sources fetch linked screens from Miniwall. |
Prices and text
| Option | Type | What it does |
|---|---|---|
products | map of package ID to ProductInfo | A price for each package ID. Fills every {{ product.* }} text. See Purchases and prices. |
variablesPerPackage | map of package ID to a map of text values | Extra {{ product.* }} values per package. They override the computed ones. |
customVariables | map of strings, numbers or booleans | Values for {{ custom.name }}. Shown as plain text, never as HTML. |
Callbacks
| Option | Called when | Return value |
|---|---|---|
onPurchase | The user taps a purchase button. Receives packageId, product, answers (web). | true or nothing: purchased. false: cancelled, the screen stays open. Throw: failure, the screen stays open and onError is called. |
onRestore | The user taps Restore purchases. The button only appears when you pass this. | true or nothing: restored. false: nothing restored, the screen stays open. |
onAction | A "Run your code" button is pressed. Receives actionId, answers. | false keeps the screen open. Anything else closes it. |
onAnswer | The user changes an answer. | none |
onClose | Web only: the visitor closes the screen. | none |
onSkip | Web only: targeting chose to show nothing. Receives reason. | none |
onOpenUrl | A link is pressed. Default: new tab (web) or system browser (mobile). | none |
onError | Something failed after the screen opened. | none |
Details: Callbacks.
Targeting
| Option | What it does |
|---|---|
attributes | What you know about the user, merged over the ones from configure and setAttributes for this call. Used by the placement's audiences. Never sent to Miniwall. See Targeting attributes. |
The result
| Result | Web, React Native, Flutter (outcome) | iOS | Android |
|---|---|---|---|
| Purchased | purchased (packageId) | .purchased(packageId, answers) | PurchaseResult.Purchased |
| Restored | restored | .restored(answers) | PurchaseResult.Restored |
| Closed | closed | .closed(answers) | PurchaseResult.Closed |
| Action | action (actionId) | .action(actionId, answers) | PurchaseResult.ActionTaken |
| Skipped | skipped (reason) | .skipped(reason) | PurchaseResult.Skipped |
answers holds what the user answered (see Answers). skipped means nothing opened; reason is frequency_cap, audience or holdout.
present throws (or the promise rejects) when the screen cannot be loaded, for example a wrong key or no network and 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;
default: break; // closed, restored or skipped
}
} catch (error) {
console.error('Could not show the screen', error);
}While it is open
- 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 presentation. The result arrives once, when the flow ends. The first screen is the one the placement returns, so its A/B test, schedule and audiences apply to the whole flow. See Answers.
- The SDK adds no close button. A screen closes with a button in its design that uses the action Close the screen, or with the platform gestures below. Closing finishes with
closed. - Links open only if they are
http,https,mailto,telor start with a single/.
Differences
Web: only one screen is presented at a time; presenting another closes the current one and the earlier promise resolves with closed. Esc closes the screen (for a banner, slide-in or toast only when focus is inside it). Popups, full screens and drawers lock page scroll, trap focus (Tab wraps) and restore focus on close. A modal's backdrop click closes it unless closeOnBackdrop: false.
iOS: swipe down closes a sheet, but the swipe is blocked while onPurchase, onRestore or onAction runs, so a result is never lost. A full-screen cover has no gesture. If nothing can be presented (no view controller or window, or UIKit is busy presenting or dismissing), onError gets the reason and present finishes with closed.
Android: tap outside a sheet or system back closes it; back closes an open sheet inside the screen first, then the screen. Full screen closes with system back.
With a placement
A placement source also applies the placement's setup in the dashboard: which screen, its A/B test, a schedule in force, audiences, frequency limit, holdout and display format. See Placements. Views, closes, purchase taps, purchases, restores, button presses and chosen answers are reported to Analytics unless you set analytics to false. See Events and privacy.