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 aAPP_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.*andpurchase.order-status.*(post-order)
| Target | Renders at |
|---|---|
checkout-contact-after | Between email and shipping address |
checkout-shipping-after | After shipping address form |
checkout-shipping-method-before | Above the Shipping Methods card |
checkout-payment-before | Above payment methods |
checkout-payment-after | Below payment methods |
checkout-order-summary-before | Top of the right-side summary |
checkout-order-summary-after | Bottom of the right-side summary |
purchase.checkout.cart-line-list.render-after | After the cart line list |
purchase.checkout.reductions.render-after | After the discount-code row |
purchase.checkout.actions.render-before | Above the place-order button |
purchase.thank-you.block.render | Order status page, below the status card — first visit after checkout only |
purchase.order-status.block.render | Order status page, below the status card — every visit |
purchase.thank-you.cart-line-list.render-after | Order status page, after the order summary line list — first visit only |
purchase.order-status.cart-line-list.render-after | Order status page, after the order summary line list — every visit |
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 underextensions.checkoutExtensions[] in your
app’s app.json:
| Field | Required | Description |
|---|---|---|
handle | yes | Unique handle for this extension within the app. |
target | yes | One of the wired slots above. |
iframeUrl | yes | HTTPS URL of the page rendered inside the iframe. |
appId | no | App id (defaults to install folder name). |
appName | no | Display name (defaults to the manifest’s name). |
settings | no | Arbitrary JSON, exposed to your iframe via query string. |
How the Host Renders It
The checkout host mounts a sandboxed iframe per matched extension: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 forBRIDGE_PING from the host before dispatching
anything. The host posts:
BRIDGE_PING action and wait for
the response — the host always replies { ok: true, host: 'checkout' }.
@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:| Action | What it does |
|---|---|
BRIDGE_PING | Handshake; returns { host: 'checkout' } |
TOAST_SHOW | Show a 200-char toast |
CART_GET | Returns {cartId, items[], itemCount, currency, note} |
CHECKOUT_TOTALS_GET | Returns subtotal / discounts / shipping / tax / finalPrice |
CUSTOMER_GET | Returns { email } |
CURRENCY_GET | Returns { currency } |
CART_LINES_CHANGE | addCartLine / updateCartLine / removeCartLine ops |
DISCOUNT_CODE_CHANGE | addDiscountCode (apply); remove not yet wired |
NOTE_CHANGE | updateNote / removeNote |
ATTRIBUTE_CHANGE | updateAttribute / removeAttribute |
ORDER_NOTE_SET | Legacy alias for updateNote |
COUPON_APPLY_REQUEST | Legacy alias for addDiscountCode |
GIFT_CARD_CHANGE | Returns { ok:false, applicable:false } (not yet wired) |
/checkout.
Resizing the Iframe
The iframe starts at 60px. PostAPP_BRIDGE_RESIZE to grow:
[60, 2000]px and applies the height to the iframe
that posted the message — there is no extensionId field on the
checkout host’s resize handler. So one iframe can’t resize another.
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:
Installing an Extension
Apps install extensions viaPOST /api/apps/install-extensions. The
payload mirrors the manifest:
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
Iframe sandbox
Iframe sandbox
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.HTTPS only
HTTPS only
iframeUrl must be HTTPS. The validator silently rejects HTTP URLs in
production.No access to payment data
No access to payment data
The bridge never exposes card numbers, payment tokens, or PII beyond
what
CART_GET / CUSTOMER_GET / CHECKOUT_TOTALS_GET return.Validate user input server-side
Validate user input server-side
Anything the customer types into your extension is in their browser.
Always validate again on your own backend before persisting.
Testing
- Install on a development store — install your app on a dev store
and reload
/checkoutto render the iframe. - Browser end-to-end — drive
/checkouton 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. - Bridge handshake — send
BRIDGE_PINGimmediately 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.