Miniwall Docs
Web SDK

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

OptionTypeDefaultWhat it does
paywallPaywallSourcerequiredA placement { placement: 'key' }, a published screen { paywallId: 'id' }, a URL string that returns the JSON, or the JSON object. See loadPaywall.
screensan object of screen ID to JSON object or URL stringnoneFor 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

OptionTypeDefaultWhat 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 drawerWhere 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.
durationnumberthe placement's, else 6Seconds a toast stays (3 to 60).
closeOnBackdropbooleantrueClose a modal or drawer when the dimmed area is clicked.
maxContentWidthstringnoneMaximum 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.
localestringfrom configure(), else the screen's defaultLanguage, 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

OptionTypeDefaultWhat it does
productsan object of package ID to ProductInfononeA price for each package ID. Fills every {{ product.* }} text. See Purchases and prices.
variablesPerPackagean object of package ID to an object of text valuesnoneExtra {{ product.* }} values per package. They override the computed ones.
customVariablesan object whose values are strings, numbers or booleansnoneValues for {{ custom.name }} in the screen's text. Shown as plain text, never as HTML.

Callbacks

OptionCalled whenReturn 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

OptionTypeWhat it does
attributesAttributesWhat 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, onRestore or onAction runs, 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. Only http, https, mailto, tel and 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.

On this page