Skip to main content

Checkout UI Extensions

A checkout extension is a sandboxed iframe rendered at a named slot in the checkout. It loads from your app’s domain, talks back to the host via the App Bridge postMessage protocol, and self-resizes through a APP_BRIDGE_RESIZE message. There is no @launchmystore/checkout-ui-extensions-react package and no custom hook surface. The host renders your iframe URL inside <iframe sandbox="allow-scripts allow-forms allow-popups allow-same-origin"> and you build whatever UI you want with plain HTML/CSS/JS — or React, if you ship your own React bundle.
For showing toasts, reading the live cart, applying discount codes, setting the order note, updating cart attributes, or changing cart lines from inside your iframe, see App Bridge for Checkout. That page documents every wired action and its postMessage payload.

Target Slots

The host renders extensions at any of the following targets. The validator in /api/apps/checkout-extensions accepts target strings matching these prefixes:
  • checkout-* (legacy slot-style, current canonical names)
  • checkout.* (bare dot-style alias)
  • purchase.checkout.* (namespaced dot-style alias)
  • purchase.thank-you.* and purchase.order-status.* (post-order)
Currently wired slots:
Targets not in this list won’t render even if the API accepts them — they’re reserved until the platform wires the matching slot.

Manifest

Declare checkout extensions under extensions.checkoutExtensions[] in your app’s app.json:

How the Host Renders It

The checkout host mounts a sandboxed iframe per matched extension:
The default height is 60px (see Resize below for how growth is applied). If your iframe fails to load — onerror, or it loads to an empty document (<body> has no children and no <title>) — the slot hides itself. There is no error message; the slot just disappears.

Bootstrap: read the App Bridge handshake

When you load, wait for BRIDGE_PING from the host before dispatching anything. The host posts:
In practice your iframe can also send a BRIDGE_PING action and wait for the response — the host always replies { ok: true, host: 'checkout' }.
You don’t need an SDK — the wire format is small enough to inline. The @launchmystore/app-bridge npm package wraps the same protocol if you’d rather use a typed client.

Wired Actions (Checkout host)

Full list with payload shapes is in App Bridge for Checkout. Quick reference: Anything else (modal, resource picker, buyer-journey intercept, shippingAddress edits, metafield writes) is not wired on the checkout host — those actions are listed in the App Bridge SDK but the response will time out after 10s on /checkout.

Resizing the Iframe

The iframe starts at 60px. Post APP_BRIDGE_RESIZE to grow. Include your extensionId (available on the iframe URL query string) so the host applies the height to the right iframe:
The extension slot that owns the iframe height filters resize messages by extensionId (a message from a different iframe is ignored) and applies Math.ceil(height) — the minimum is the 60px default and there is no upper clamp on this path. (A separate App Bridge checkout handler additionally clamps to [60, 2000]px for iframes it manages directly.)

Reading Settings From app.json

The host does NOT forward settings into the iframe URL automatically. If your extension needs runtime config, encode it in your own iframe URL when you install:
Or fetch the install record from your backend by passing the merchant’s domain in the iframe and looking up settings server-side.

Installing an Extension

Extensions are deployed by the install pipeline via POST /api/apps/install-extensions. This is a server-to-server endpoint gated by the platform’s internal API key (no CORS) — it’s invoked by the backend during the app install flow, not called directly from a browser with a merchant access token. The only browser-accepted variant is a fromCatalog: true catalog copy, which also requires the merchant to own the target store. The payload mirrors the manifest:
Local-only installs (file-system manifests under extensions/{domainSlug}/{appHandle}/app.json) are also picked up by /api/apps/checkout-extensions — useful for dev/test apps.

Caching

/api/apps/checkout-extensions returns a 5-minute Cache-Control header and the checkout client caches the extension list per session. After publishing a manifest change, the merchant won’t see it for up to 5 minutes unless they hard-reload.

Security

Extensions render in a sandbox with only allow-scripts allow-forms allow-popups allow-same-origin. They cannot break out, drive the parent DOM, or read the parent’s cookies — the only cross-frame channel is the App Bridge postMessage protocol.
Serve iframeUrl over HTTPS so it loads on the HTTPS checkout. The install pipeline does not enforce the scheme — absolute URLs pass through untouched (local dev may resolve to http) — so HTTPS is your responsibility.
The bridge never exposes card numbers, payment tokens, or PII beyond what CART_GET / CUSTOMER_GET / CHECKOUT_TOTALS_GET return.
Anything the customer types into your extension is in their browser. Always validate again on your own backend before persisting.

Testing

  1. Install on a development store — install your app on a dev store and reload /checkout to render the iframe.
  2. Browser end-to-end — drive /checkout on your dev store with a headless browser and screenshot the slot. JSON manifests prove the install endpoint works; only a real screenshot proves the iframe rendered and the bridge handshake completed.
  3. Bridge handshake — send BRIDGE_PING immediately on load and log the response. If the response never arrives the parent isn’t the checkout host (e.g. you’re previewing in a standalone tab) and you should render a graceful “preview mode” fallback.

See Also

  • App Bridge for Checkout — full action payload reference for the checkout host.
  • Post-Purchase Extensions — same iframe model on the order status (thank-you) page; smaller wired-action set.
  • Functions — declarative business logic that runs server-side instead of as a UI iframe.