View as Markdown

JavaScript API Reference

The onsite script exposes window.ezsubscriptions after https://sm.ezoic.com/min.js loads. Because the script loads asynchronously, put API calls inside the cmd queue:

<script>
  window.ezsubscriptions = window.ezsubscriptions || {};
  ezsubscriptions.cmd = ezsubscriptions.cmd || [];
  ezsubscriptions.cmd.push(function (api) {
    // api is the resolved ezsubscriptions API.
  });
</script>
<script src="https://sm.ezoic.com/min.js" async defer></script>

Methods that return promises can reject if a network request fails. Wrap access checks and checkout-launching calls in try / catch when your page needs a custom fallback.

Most calls take a product handle or a price handle — the stable keys you author on a product and its prices in your Ezoic dashboard. See Products, Prices, and Paid Access for how those keys are created.

Examples here assume the default Ezoic visitor accounts. A few methods behave differently under bring your own login — notably login(), logout(), initialize(), and authChanged(). See Visitor Authentication for the BYO handling.

Readiness

ezsubscriptions.cmd

Queues callbacks until the script is ready. Each callback receives the resolved API object.

ezsubscriptions.cmd.push(function (api) {
  api.showPaywall({ product: "remove-ads" });
});

Callbacks pushed after the script is ready run immediately.

ezsubscriptions.ready

Boolean value that is true after the API is ready.

if (window.ezsubscriptions?.ready) {
  window.ezsubscriptions.showPaywall({ product: "remove-ads" });
}

Access Methods

hasAccess(query)

hasAccess(query: string | { product: string }): Promise<AccessResponse>

Checks whether the current visitor holds an active entitlement for a product. Pass a product handle — the whole-product access key, not a price handle or an item key.

const access = await ezsubscriptions.hasAccess("remove-ads");

You can pass the product handle directly or as an object:

await ezsubscriptions.hasAccess("remove-ads");
await ezsubscriptions.hasAccess({ product: "remove-ads" });

Parameters:

  • query: string | { product: string } — the product handle authored on your product.

Returns:

{
  decision: "allowed" | "denied" | "login_required" | "expired" | "revoked" | "unknown_product",
  reasonCode: string
}

Show protected content only when decision is allowed. Treat every other decision as no current access.

Anonymous visitors return login_required without a network request.

Call hasAccess when your page or view renders — not on a timer. Every access change the widget can observe (login, logout, completed checkout) is pushed to you through access:change; polling with setInterval only adds requests without catching anything the event misses.

This promise can reject if the visitor is signed in and the access request fails.

hasPurchased(query)

hasPurchased(query: { item: string } | { page: true }): Promise<AccessResponse>

Checks whether the current visitor has bought a one-time per-item purchase. The item is a site-wide key matched on its own — no price needed. It is the read counterpart to openCheckout({ price, item }) and showPaywall({ product }).

// A single item you name:
const access = await ezsubscriptions.hasPurchased({ item: "article-12345" });

// The current article (a price set to unlock the current article automatically):
const access = await ezsubscriptions.hasPurchased({ page: true });

if (access.decision === "allowed") {
  revealArticle();
}

Parameters:

  • query: { item: string } | { page: true }
  • query.item — the item key you passed to openCheckout({ price, item }) / showPaywall({ product, item }). It is matched exactly, across the visitor's one-time purchases on the site, independent of which price sold it.
  • query.page — pass { page: true } instead of an item to check the current page. Ezoic resolves the same page key it stamped at checkout for a price set to unlock the current article automatically, so you never compute it yourself.

Returns the same AccessResponse shape as hasAccess.

Anonymous visitors return login_required without a network request. A missing item returns denied without a network request.

getProducts()

getProducts(): Promise<string[]>

Returns the product handles the visitor currently holds.

const products = await ezsubscriptions.getProducts();
if (products.includes("pro")) {
  enableProFeatures();
}

Parameters: none.

Returns:

string[]

Anonymous visitors return [] without a network request.

This promise can reject if the visitor is signed in and the request fails.

getPurchases()

getPurchases(): Promise<Purchase[]>

Returns the visitor's purchases — for building a "your purchases", downloads, or library page. Unlike getProducts(), the list includes one-time per-item purchases.

const purchases = await ezsubscriptions.getPurchases();
purchases
  .filter((purchase) => purchase.status === "active")
  .forEach((purchase) => renderLibraryRow(purchase));

Parameters: none.

Returns:

Array<{
  productKey?: string,
  item?: string,
  status: string,
  expiresAt?: string
}>
  • productKey is the product the purchase belongs to. It is omitted if that product was removed.
  • item is set only for a one-time per-item purchase.
  • status lets you filter active access from expired or revoked entitlements.
  • expiresAt is set only for time-limited access; lifetime access omits it.

Anonymous visitors return [] without a network request. This promise can reject if the visitor is signed in and the request fails.

Ad Removal Methods

An ad-free experience is the most common paid benefit. After hasAccess(...) returns allowed, call disableAds(); when access is lost, call allowAds(). The widget owns the whole mechanism — you never touch ad placeholders or cookies. See Removing Ads for Subscribers for the full pattern.

disableAds()

disableAds(): Promise<void>

Suppresses every ad format for an entitled visitor — display, floating video, and interstitials. The widget sets a signed cookie that Ezoic reads server-side, so ads never load rather than being torn down after they render.

const access = await ezsubscriptions.hasAccess("remove-ads");
if (access.decision === "allowed") {
  ezsubscriptions.disableAds();
}

Parameters: none.

Returns:

Promise<void>

On the page where access is first gained (typically right after checkout or login), disableAds() reloads once so the server can apply suppression; every page view after that is ad-free with no reload. When access is gained inside checkout, the reload waits until the visitor closes the confirmation screen; for a mid-article login, their scroll position is preserved. If the visitor is not entitled or the cookie cannot be set, it fails open and leaves ads in place.

A mounted checkout never triggers this reload: while it is on the page (during checkout, on the confirmation screen, and during account creation), disableAds() sets the cookie without reloading, and destroy() does not reload either. Ads stop on the visitor's next page view.

allowAds()

allowAds(): void

Removes the ad-suppression cookie when access is lost — logout, an expired subscription, or a revoked entitlement. It does not reload; ads return on the visitor's next page view.

const access = await ezsubscriptions.hasAccess("remove-ads");
if (access.decision !== "allowed") {
  ezsubscriptions.allowAds();
}

Parameters: none.

Returns: void.

Paywall and Checkout Methods

onSuccess is deprecated on showPaywall and openCheckout; mountCheckout has no onSuccess. A login or payment redirect loses the callback. Check hasAccess(...) or hasPurchased(...) on page load and after access:change instead. onCancel and onError still report dismissals and failed attempts on the page showing checkout.
With expedited checkout on (the default), an anonymous guest pays on a single screen (wallet buttons where available, an email field, and the card form) instead of stepping through a separate sign-in flow first.

showPaywall(options)

showPaywall(options: {
  product: string;
  item?: string;
  dismissible?: boolean;
  email?: string;
  promoCode?: string;
  onSuccess?: (result: CheckoutResult) => void;
  onCancel?: () => void;
  onError?: (error: CheckoutError) => void;
}): Promise<void>

Opens Ezoic's pre-built paywall and checkout experience for a product and its active prices. It takes a product handle (what you sell), not a price handle; item scopes a one-time single-item purchase and is matched later by hasPurchased({ item }).

await ezsubscriptions.showPaywall({
  product: "remove-ads",
  onError: function (error) {
    console.log(error.message);
  },
});

Parameters:

  • options?: object
  • options.product: string — the product handle to present. Required: without it the call does nothing.
  • options.item?: string — your item key for a one-time single-item price that sells an item your site provides: the purchase is scoped to that item and matched later by hasPurchased({ item }). These prices don't appear in the paywall unless you pass an item — without one there is nothing to scope the purchase to. Prices that unlock the current article automatically ignore options.item — they always appear and key the purchase to the page the paywall is shown on.
  • options.dismissible?: boolean — when false, the paywall is blocking: no close button, no Escape, and onCancel cannot fire. Defaults to true (dismissible).
  • options.email?: string — prefills the guest email field, for example with an address your page already collected. The visitor still confirms it before payment.
  • options.promoCode?: string — pre-applies a promo code: the plan picker shows the discounted prices and the code applies itself at checkout. An invalid code shows its reason at checkout and logs a console error, but never blocks checkout. See Campaign Links.
  • options.onSuccess?: (result: CheckoutResult) => void — deprecated: fires after checkout completes and access is established, but does not survive a page redirect. Use access:change instead — see the note above.
  • options.onCancel?: () => void — fires when a dismissible paywall is closed before checkout completes.
  • options.onError?: (error: CheckoutError) => void — fires when a checkout attempt fails. It may fire more than once if the visitor retries.

Returns:

Promise<void>

The widget loads the product's active prices, re-checks visitor access, and renders nothing if the visitor already holds the product or already bought the item in play, or if the product has no active prices to sell. If the product config cannot be loaded, the widget shows a dismissible error message and fires onError.

openCheckout(options)

openCheckout(options: {
  price: string;
  item?: string;
  dismissible?: boolean;
  email?: string;
  promoCode?: string;
  onSuccess?: (result: CheckoutResult) => void;
  onCancel?: () => void;
  onError?: (error: CheckoutError) => void;
}): Promise<void>

Launches checkout directly for a single price handle, skipping the paywall's product and price selection. Use it for custom "buy" buttons wired to a specific price. item scopes a one-time single-item purchase; it is only meaningful for an item-scoped one-time price.

await ezsubscriptions.openCheckout({
  price: "remove-ads-monthly",
});

To react to a completed checkout — for example, reload the page — subscribe to access:change and re-check access; see the note above for why onSuccess is deprecated.

Parameters:

  • options: object
  • options.price: string — the price handle to charge. Required: without it the call does nothing.
  • options.item?: string — scopes a one-time per-item purchase; the value is stored verbatim as the entitlement key and matched exactly by hasPurchased({ item }). Optional for a product-scoped price, but required for an item-scoped price — checkout is rejected without it.
  • options.dismissible?: boolean — when false, checkout is blocking: no close button, no Escape, and onCancel cannot fire. Defaults to true (dismissible).
  • options.email?: string — prefills the guest email field, for example with an address your page already collected. The visitor still confirms it before payment.
  • options.promoCode?: string — pre-applies a promo code at checkout. Behaves like the showPaywall option — see Campaign Links.
  • options.onSuccess?: (result: CheckoutResult) => void — deprecated — see the note above
  • options.onCancel?: () => void
  • options.onError?: (error: CheckoutError) => void

Returns:

Promise<void>

mountCheckout(options)

mountCheckout(options: {
  target: HTMLElement | string;
  price: string;
  item?: string;
  email?: string;
  promoCode?: string;
  successUrl?: string;
  onError?: (error: CheckoutError) => void;
}): Promise<MountedCheckout>

interface MountedCheckout {
  destroy(): void;
}

Renders checkout for a price handle inside a page element, using the same sign-in and payment flow as openCheckout.

<div id="checkout"></div>

<script>
  window.ezsubscriptions = window.ezsubscriptions || {};
  ezsubscriptions.cmd = ezsubscriptions.cmd || [];
  ezsubscriptions.cmd.push(function (api) {
    api.mountCheckout({
      target: "#checkout",
      price: "premium-monthly",
    }).catch(function (error) {
      console.error("Checkout could not mount", error.code, error.message);
    });
  });
</script>
<script src="https://sm.ezoic.com/min.js" async defer></script>

Parameters:

  • options.target: HTMLElement | string — required. A connected HTML element in the current document, or a CSS selector matching exactly one. The widget adds its own child without replacing your content. Targets inside a shadow root are not supported.
  • options.price: string — required. The price handle to sell. Same as openCheckout.
  • options.item?: string — required for prices that unlock an item your site provides. Prices that unlock the current article automatically use the current page and ignore item. Product-scoped prices reject a supplied item.
  • options.email?: string — prefills the guest email field.
  • options.promoCode?: string — pre-applies a promo code.
  • options.successUrl?: string — destination for No thanks, continue or Continue after checkout. See Success destination.
  • options.onError?: (error: CheckoutError) => void — fires when something fails after the checkout has been placed on the page: its configuration can't load, the price or item is invalid, or a payment attempt fails. The error is also shown inside the checkout. It may fire more than once.

There is no onSuccess or onCancel. Use access checks and access:change to detect access; use destroy() to remove checkout.

The promise resolves once the loading placeholder is mounted, so you can call destroy() while checkout loads. Resolution does not mean payment succeeded.

Invalid options or an active checkout reject the promise without calling onError. The error is a MountCheckoutError with a string code:

code Cause
invalid_target The selector is invalid or matches zero or several elements, or the element is detached, in another document, inside a shadow root, or an element that can't hold children (such as <img> or <input>).
invalid_price price is missing or empty. An unknown or inactive price handle is reported later through onError instead.
invalid_success_url successUrl is not a valid same-origin http/https URL.
checkout_active A mounted checkout is already on the page, or a paywall, checkout, donation, or login dialog is open or opening.

A rejected call adds no checkout to your page.

While checkout is mounted, including its confirmation screen, mountCheckout, showPaywall, openCheckout, openDonation, and login() reject with checkout_active. Call destroy() before opening another checkout or login dialog. Sign-in and account creation within checkout remain available. Declarative login and donation buttons log this error to the console.

What the visitor sees in the element:

  • A loading state, then the checkout. If the visitor already has access to this price's product or item, they see You already have access instead of a payment form.
  • After payment, confirmation and optional account creation stay in the element until it is destroyed or the visitor leaves the page.
  • Checkout has no close button, backdrop, or Escape-to-close. Back moves between checkout steps. Stripe may open a wallet or verification dialog that temporarily blocks page scrolling.

Mounted checkout uses the price's parent product's Appearance settings, like openCheckout, and sizes itself to your element's width. To restyle it, set CSS variables on [data-ezoic-sm-checkout] — see Styling a Mounted Checkout.

Stripe wallet verification that fails or is cancelled can leave page scrolling locked. Include wallet and verification checks in your testing.

Success destination

With successUrl, the account-creation prompt and password form show No thanks, continue as a link to that URL. After account creation, or when no account prompt is needed, Continue links there instead. You already have access uses the same destination. Navigation happens only when clicked.

Without successUrl, skipping account creation leaves the confirmation visible and no navigation link is added. There is no default destination; pass "/" for your home page.

Relative URLs resolve against the current page's address. The destination must use http or https, match the page's origin, and contain no username or password. Invalid destinations reject with invalid_success_url.

destroy()

Removes the checkout from your element and releases it so another checkout can open. It is safe to call more than once, and a handle from an earlier mount cannot remove a later one.

destroy() only removes the interface. It does not cancel, refund, or sign anyone out: a submitted payment may still complete. hide() also removes a mounted checkout; closeDonation() does not.

In a single-page app, call destroy() before removing the target:

let disposed = false;
let checkout;
ezsubscriptions.mountCheckout({
  target: "#checkout",
  price: "premium-monthly",
  successUrl: "/",
}).then(function (handle) {
  if (disposed) {
    // The view was torn down before the promise resolved.
    handle.destroy();
    return;
  }
  checkout = handle;
}).catch(function (error) {
  console.error("Checkout could not mount", error);
});

function unmountPage() {
  disposed = true;
  checkout?.destroy();
}

Reacting to a mounted checkout

Check access when the page loads and after each access:change event. The initial check detects existing access; subscribing first catches changes while that check is running. See the integration example.

Use the product handle with hasAccess, or an item key with hasPurchased. Only decision === "allowed" grants access; the event and return URL are not proof of payment. Keep the confirmation mounted while updating content so the visitor can finish account creation.

In a single-page app, unsubscribe from access:change when the view is removed, alongside checkout.destroy().

Redirects

Sign-in, HTTPS setup, or Stripe verification can navigate away from checkout. The widget does not automatically remount checkout when the visitor returns; your return page must call mountCheckout and check access again.

If bring-your-own login finishes without leaving the page, call destroy() before remounting. The existing checkout still owns the target until destroyed.

Login and Logout

login(options)

login(options?: { dismissible?: boolean }): Promise<void>

Opens a login screen so a returning subscriber can sign in without first hitting a paywall — wire it to a "Log in" link in your own navigation. On a successful login the widget establishes the access session and fires access:change, so gated content unlocks in place with no page reload.

document.getElementById("log-in").addEventListener("click", function () {
  ezsubscriptions.login();
});

login() is mode-aware:

  • Ezoic visitor accounts — opens Ezoic's login screen in the widget (a one-time email sign-in link, Continue with Google when available, and password sign-in for visitors who set one).
  • Bring your own login — forwards to your adapter's goToLogin(); the widget renders no login UI of its own. See Visitor Authentication for the adapter setup and the authChanged() call that goes with it.

You can also trigger it declaratively, with no JavaScript, using the data-ezoic-login attribute.

Parameters:

  • options?: object
  • options.dismissible?: boolean — whether the visitor can close the login screen. Defaults to true.

Returns:

Promise<void>

If the visitor already has an active session, login() does nothing (and re-fires access:change). On a bring-your-own-login domain with no registered adapter, it is a no-op.

logout()

logout(): Promise<void>

Ends the visitor's access session — the counterpart to login(). It clears the Subscriptions access session and fires access:change so gated content re-locks in place. Wire it to a "Log out" link, or use the declarative data-ezoic-logout attribute.

document.getElementById("log-out").addEventListener("click", function () {
  ezsubscriptions.logout();
});

Call logout() only when the visitor explicitly asks to sign out — never in response to an access check. A denied, expired, or revoked decision from hasAccess(...) means "no access to this product", not "should be signed out". A signed-in visitor without the product still needs their session to buy it: signing them out mid-flow revokes the session their in-progress checkout depends on, and the purchase fails.

If you granted an ad-free experience with ezsubscriptions.disableAds(), logout() restores ads too — see Signing Out.

logout() is mode-aware:

  • Ezoic visitor accounts — also signs the visitor out of their Ezoic visitor account behind the scenes, so they are not silently re-authenticated on the next page load.
  • Bring your own login — the widget cannot end your own login session, so sign the visitor out of your system first, then call logout(). Calling authChanged() after your sign-out also clears the access session, but unlike logout() it does not restore ads.

Returns:

Promise<void>

See Signing Out for the full lifecycle across both modes. The subscriber portal (subscriber.ezoic.com) has its own separate sign-out.

Authentication Methods

These methods apply to Bring Your Own Login integrations. See Visitor Authentication for the full setup, including the auth adapter contract. Sites on Ezoic visitor accounts do not need them.

initialize(config)

initialize(config: { auth: AuthAdapter }): void

Registers your auth adapter so the widget can resolve the logged-in visitor's identity from your own login system.

ezsubscriptions.initialize({
  auth: {
    getUserEmail: () => currentUser?.email ?? null,
    goToLogin: () => location.assign("/login"),
    goToCreateAccount: () => location.assign("/signup"),
  },
});

Parameters:

  • config: { auth: AuthAdapter } — the adapter object. See Visitor Authentication for the adapter method contract.

Returns: void.

Safe to call repeatedly; a later valid call replaces the adapter.

authChanged()

authChanged(): Promise<void>

Signals that your auth state changed — a login, logout, account switch, or async session restore. The widget re-resolves identity through the adapter, refreshes or clears its session, and fires access:change.

await ezsubscriptions.authChanged();

Parameters: none.

Returns:

Promise<void>

Call this instead of re-running initialize for runtime auth changes.

on(event, handler) / off(event, handler)

on(event: "access:change", handler: () => void): () => void
off(event: "access:change", handler: () => void): void

Subscribe to access:change so gated UI re-renders when the visitor's access may have changed (login, logout, account switch, completed checkout).

const unsubscribe = ezsubscriptions.on("access:change", async function () {
  const access = await ezsubscriptions.hasAccess("remove-ads");
  if (access.decision === "allowed") {
    ezsubscriptions.disableAds();
  } else {
    ezsubscriptions.allowAds();
  }
});

// Later, on cleanup:
unsubscribe();

Parameters:

  • event: "access:change" — the only supported event.
  • handler: () => void — runs when access may have changed. Takes no arguments; re-read access inside it.

on returns an unsubscribe function. off(event, handler) detaches a handler by reference and returns void.

Analytics Methods

trackEvent(name, value)

trackEvent(name: string, value?: string): void

Emits a custom analytics event — for example trackEvent("cta_click", "homepage-hero"). Custom events appear alongside the checkout funnel in your dashboard's Subscriptions analytics. Fire-and-forget: it never throws. Like every API call, make it inside the cmd queue (or after ready) — before the script loads, ezsubscriptions is only the queue:

ezsubscriptions.cmd.push(function (api) {
  api.trackEvent("pricing_view", "homepage");
});

Parameters:

  • name: string — the event name.
  • value?: string — an optional label to attach.

Keep value low-cardinality: a label such as "membership" or "header-cta", never a unique id, hash, or URL. The analytics table groups events by name and value and shows the most frequent values per name, so a unique value per event (a document id, a user id) buries the useful rows. The page URL is already recorded with every event — you do not need to put it in value.

trackEvent adds your own context beside the funnel; it is not a conversion API. Completed checkouts are already reported as checkout_completed in the same analytics — a client-sent "purchased" event would double-count them.

Returns: void.

Account Methods

openAccountPortal()

openAccountPortal(): void

Opens the subscriber portal at https://subscriber.ezoic.com in a new tab, where a logged-in member manages payment methods, receipts, and subscriptions. Wire this to a "Manage subscription" link for members.

document.getElementById("manage-subscription").addEventListener("click", function () {
  ezsubscriptions.openAccountPortal();
});

Parameters: none.

Returns: void.

On Ezoic visitor accounts, a signed-in member opens the portal already signed in, with no additional login. On bring your own login, a signed-in member also opens it already signed in when your adapter implements getIdentityToken(); without it, and for guest or unrecognized visitors, this is plain navigation and the portal runs its own sign-in.

getSessionToken()

getSessionToken(): string | null

Returns the signed-in reader's session token (a domain-scoped JWT), or null when the reader is signed out or the session has expired.

This is the client half of the Server-to-Server REST API. On Ezoic-visitor-account sites your origin server never sees the reader's email, so you relay this token instead: read it on the page, send it to your backend, and forward it to the REST API as the X-Ezoic-Reader-Token header.

ezsubscriptions.cmd.push(function (api) {
  const token = api.getSessionToken();
  if (token) {
    fetch("/my-backend/unlock", { headers: { "X-Reader-Token": token } });
  }
});

Parameters: none.

Returns:

string | null

The token is short-lived and scoped to the current domain, so read it per request rather than caching it. See the REST API's Reader Identity section for the full relay flow.

Donation Methods

openDonation(options)

openDonation(options?: {
  amountCents?: number;
  productId?: string;
  onSuccess?: (result: CheckoutResult) => void;
  onCancel?: () => void;
  onError?: (error: CheckoutError) => void;
}): Promise<void>

Opens the donation checkout. This is the single donation entry point: it loads your donation settings the first time it's called, so no setup call is required and it works no matter when you call it. On a site with no donations configured it does nothing (and logs a warning). A [data-ezoic-donate] element opens the same dialog with no JavaScript, and accepts data-ezoic-product-id and data-ezoic-amount-cents attributes matching the productId and amountCents options:

<button type="button" data-ezoic-donate data-ezoic-amount-cents="2500">Give $25</button>
ezsubscriptions.openDonation({
  amountCents: 2500,
  onSuccess: function (result) {
    console.log("Donation complete", result.amountCents);
  },
});

Parameters:

  • options?: object
  • options.amountCents?: number — preselected donation amount in the smallest unit of the currency the visitor's picker presents (cents for USD/EUR). For example, 2500 means $25.00 for a visitor seeing dollars. The configured minimum still applies.
  • options.productId?: string — optional donation product ID. Most donation integrations should omit this because a site has one active donation in the current dashboard flow.
  • options.onSuccess?: (result: CheckoutResult) => void — fires after donation checkout completes. Donations grant no entitlement, so this is the only completion signal — there is no access:change to key off. Treat it as best-effort: a full-page 3D Secure redirect drops it.
  • options.onCancel?: () => void — fires when the visitor closes the donation dialog before checkout completes.
  • options.onError?: (error: CheckoutError) => void — fires when a checkout attempt fails. It may fire more than once if the visitor retries.

Returns:

Promise<void>

If amountCents is missing, invalid, or below the configured minimum, the widget falls back to the normal donation picker.

closeDonation()

closeDonation(): void

Closes the donation dialog.

ezsubscriptions.closeDonation();

Parameters: none.

Returns: void.

Hiding the Widget

hide()

hide(): void

Closes the paywall, checkout, donation, and login screens, and removes a mounted checkout as if you had called its destroy().

ezsubscriptions.hide();

Parameters: none.

Returns: void.

Callback Payloads

CheckoutResult

Passed to onSuccess (showPaywall, openCheckout, openDonation). Mounted checkout never produces one.

{
  productType?: "access" | "donation",
  productExternalId?: string,
  priceExternalId?: string,
  amountCents?: number,
  product?: string,
  price?: string,
  item?: string
}
  • amountCents is the requested base amount in cents. Taxes, discounts, or payment-provider adjustments can change the final charged total. Set for showPaywall checkouts (including per-item purchases); omitted for openCheckout, along with productType, productExternalId, and priceExternalId.
  • productType is access for any subscription or one-time product (legacy type names are normalized to access server-side) and donation for donations.
  • product is set when checkout was opened with showPaywall({ product }).
  • price is set when checkout was opened with openCheckout({ price }).
  • item is set for a one-time per-item purchase — openCheckout({ price, item }), showPaywall({ product, item }), or a paywall price that unlocks the current article automatically.
  • A callback that throws is logged and does not break the widget.

CheckoutError

Passed to onError.

{
  message: string,
  code?: string
}

Checkout stays open for retry after an error, so onError can fire more than once. For a failed payment, code is the payment provider's error code (for example card_declined).

Loading and configuration errors in a mounted checkout report a message through onError; they do not have the preflight rejection codes listed under MountCheckoutError.

MountCheckoutError

The rejection value of mountCheckout (and of showPaywall, openCheckout, openDonation, and login while a mounted checkout is active):

Error & {
  code: "invalid_target" | "invalid_price" | "invalid_success_url" | "checkout_active";
}

See mountCheckout for the codes.