App Bridge for Checkout
Checkout extensions render as iframes inside the customer-facing checkout (/checkout). They use the same App Bridge wire format as admin extensions, but the host on /checkout exposes a customer-safe subset of actions — no resource pickers, no admin modals, no title bars.
Use this page when building any extension whose target starts with checkout-*, checkout.*, or purchase.checkout.*.
What’s different from admin App Bridge
| Capability | Admin host | Checkout host (LaunchMyStore) | Post-purchase host (LaunchMyStore) |
|---|---|---|---|
| Wire format | APP_BRIDGE_ACTION / APP_BRIDGE_RESPONSE | Same | Same |
| SDK package | @launchmystore/app-bridge | Same | Same |
TOAST_SHOW | ✓ | ✓ | ✓ |
BRIDGE_PING | — | ✓ (host: 'checkout') | ✓ (host: 'post-purchase') |
CART_GET / CHECKOUT_TOTALS_GET | — | ✓ | — |
CART_LINES_CHANGE | — | ✓ (add / update / remove) | ✓ (add only) |
DISCOUNT_CODE_CHANGE | — | ✓ (add) | — |
NOTE_CHANGE, ATTRIBUTE_CHANGE | — | ✓ | — |
GIFT_CARD_CHANGE | — | ✓ (stub — returns applicable: false) | — |
COST_GET, BUYER_IDENTITY_GET, LOCALIZATION_GET | — | ✓ | — |
SHIPPING_ADDRESS_GET, BILLING_ADDRESS_GET | — | ✓ | — |
DELIVERY_GROUPS_GET, SHIPPING_ADDRESS_CHANGE | — | ✓ | — |
DISCOUNT_CODES_GET, APPLIED_GIFT_CARDS_GET | — | ✓ | — |
SHOP_GET, INSTRUCTIONS_GET, ATTRIBUTES_GET | — | ✓ | — |
METAFIELD_CHANGE | — | ✓ (stub — returns error) | — |
ORDER_NOTE_SET / COUPON_APPLY_REQUEST (legacy) | — | ✓ | — |
ORDER_GET | — | — | ✓ |
CUSTOMER_GET / CURRENCY_GET | — | ✓ | ✓ |
CLIPBOARD_WRITE | ✓ | — | ✓ |
REDIRECT | ✓ | — | ✓ |
DONE (goes to /orders/<id>) | — | — | ✓ |
BUYER_JOURNEY_INTERCEPT_REQUEST/RESPONSE | — | ✓ | — |
RESOURCE_PICKER_OPEN | ✓ | ✗ (merchant-only) | ✗ |
TITLE_BAR_UPDATE, NAV_MENU_UPDATE | ✓ | ✗ (no admin chrome) | ✗ |
MODAL_OPEN, CONTEXTUAL_SAVE_BAR | ✓ | ✗ (would block checkout) | ✗ |
SESSION_TOKEN_REQUEST | ✓ | ✗ (no admin session at checkout) | ✗ |
SCANNER_OPEN, FULLSCREEN_* | ✓ | ✗ | ✗ |
Initializing
If your extension iframe loads from a URL the merchant configured inapp.json, the host will not pass apiKey or host query parameters by default. You can use the lightweight client below, or initialize the SDK manually with the parent origin:
Action Reference
BRIDGE_PING
Capability handshake. Resolves with{ ok: true, host: 'checkout' } if the checkout host is listening.
TOAST_SHOW
Show a toast notification rendered by the checkout’s toast system.| Field | Type | Required | Description |
|---|---|---|---|
message | string | Yes | Up to 200 chars (longer messages are truncated) |
variant | 'success' | 'error' | No | Default success |
{ ok: true }
CART_GET
Read the current cart contents.cartId field exposes the value of the cart cookie so your extension can hit /api/cart/* endpoints directly without re-reading cookies. The cookie itself is non-HttpOnly (document.cookie works too), but going through the bridge keeps your code portable.
CHECKOUT_TOTALS_GET
Read the live price breakdown from the verified cart. This is the same data the host renders in the order summary, so values stay consistent.CUSTOMER_GET
Read the email the customer has typed at checkout. No PII beyond what the customer has already entered into the host.email is an empty string. The customer is not necessarily logged in; do not assume this maps to a Customers row.
CURRENCY_GET
CART_GET().currency — exposed separately so simple extensions don’t have to fetch the whole cart.
COST_GET
Returns the live cost breakdown as standard{ amount, currencyCode } money objects (suitable for currency-formatted display). For the flat scalar breakdown used by the order summary, see CHECKOUT_TOTALS_GET.
CHECKOUT_TOTALS_GET, this is a snapshot — there is no push notification. Re-poll after any cart / discount / address change to stay in sync.
BUYER_IDENTITY_GET
The minimal identity the checkout has collected so far. Useful for personalising extension copy without prompting the customer.LOCALIZATION_GET
Country, language, and currency picked by the customer (or inherited from the store’s auto-detected locale).SHIPPING_ADDRESS_GET
The customer’s selected delivery address. Returnsaddress: null until they pick / type one.
BILLING_ADDRESS_GET
LaunchMyStore checkout does not split shipping/billing — this returns the sameaddress payload as SHIPPING_ADDRESS_GET.
DISCOUNT_CODES_GET
Coupon codes the customer has applied to this cart.APPLIED_GIFT_CARDS_GET
Gift cards aren’t fully wired yet — always returns an empty array. Apps can call this safely to detect a gift-card-free state without hanging.DELIVERY_GROUPS_GET
Available shipping options for the current cart and address. Mirrors what the order summary renders next to “Shipping”.SHOP_GET
Identity of the store the checkout is running on.storeDomain is the store’s canonical LaunchMyStore domain. Custom domains aren’t reflected here — use storefrontUrl for the public-facing URL the customer is on.
INSTRUCTIONS_GET
Capability flags describing what the current checkout host allows your extension to mutate. Use this to feature-gate your UI instead of guessing or hard-coding.canRemoveDiscountCodes / giftCards.canUpdate / metafields.canUpdate are false today — calling the matching mutation actions will return a deterministic “not supported” error so you don’t hang on a timeout.
ATTRIBUTES_GET
All cart-level attributes apps and other extensions have written.order.attributes.
ORDER_NOTE_SET
Write a delivery note / order instruction. Persisted to the cart’snote field and saved on the order when it’s placed.
| Field | Type | Required | Description |
|---|---|---|---|
note | string | Yes | Truncated to 500 chars |
COUPON_APPLY_REQUEST
Forward a coupon code to the host’s coupon input.| Field | Type | Required | Description |
|---|---|---|---|
code | string | Yes | Truncated to 64 chars |
code. The customer still confirms by clicking Apply in the native UI — your extension does not bypass merchant coupon rules.
Cart Mutation Actions
The checkout host exposes a declarative cart-mutation surface: an extension calls one of the actions below, the host applies the mutation to the cart, re-runs cart verification, and responds with the updated cart envelope. Mutations follow a stable, action-builder-style call shape so extensions stay portable across hosts. Use theCart class from @launchmystore/app-bridge for type safety; the
raw action names are documented below for reference.
CART_LINES_CHANGE
Add, update, or remove cart lines. Accepts a single change or an array.| Field | Type | Required | Notes |
|---|---|---|---|
type | 'addCartLine' | 'updateCartLine' | 'removeCartLine' | yes | |
merchandiseId | string (gid) | for addCartLine | gid://launchmystore/Variant/<uuid> |
id | string (gid) | for update/remove | gid://launchmystore/CartLine/<uuid> |
quantity | integer | for add/update | Updates with 0 remove the line |
attributes | {key,value}[] | no | Per-line properties — stored on the order line |
addCartLine targets a variant already in the order summary, the
new line is enriched from existing cart / verified-cart data so name, image,
and price aren’t lost — important for post-purchase upsells where the
line is added from a different page.
DISCOUNT_CODE_CHANGE
Apply or remove a discount code.| Field | Type | Required | Notes |
|---|---|---|---|
type | 'addDiscountCode' | 'removeDiscountCode' | yes | removeDiscountCode currently returns error: 'not supported yet' |
code | string | yes | Trimmed to 64 chars |
applyCoupon flow — eligibility, expiry, and min-cart checks
all apply. The host responds with { ok: true, code } once the apply
attempt completes.
NOTE_CHANGE
Update or remove the cart note.| Field | Type | Required | Notes |
|---|---|---|---|
type | 'updateNote' | 'removeNote' | yes | |
note | string | for updateNote | Truncated to 500 chars |
note
field at placement.
ATTRIBUTE_CHANGE
Set or remove a cart-level attribute (key/value pair stored on the order).| Field | Type | Required | Notes |
|---|---|---|---|
type | 'updateAttribute' | 'removeAttribute' | yes | |
key | string | yes | Truncated to 64 chars |
value | any | for update | JSON-serialisable |
GIFT_CARD_CHANGE (stub)
Gift cards aren’t fully wired yet. Calling this action returns{ ok: false, applicable: false } so apps can detect unsupported state
without hanging on a 10-second timeout. The action name is reserved
for forward compatibility — when gift cards land, the contract will
follow the standard applyGiftCardChange shape.
SHIPPING_ADDRESS_CHANGE
Update the customer’s shipping address. OnlyupdateShippingAddress is supported today — there is no add/remove distinction because checkout holds exactly one address at a time.
| Field | Type | Required | Notes |
|---|---|---|---|
type | 'updateShippingAddress' | yes | Only one type today |
address | object | yes | Field names match SHIPPING_ADDRESS_GET output |
'address required'— noaddressobject passed'address mutation not available'— the host page hasn’t wired the writer (e.g. read-only screens)'unknown address change type'—typeis notupdateShippingAddress
METAFIELD_CHANGE (stub)
Cart-level metafields aren’t stored yet — the backend metafields surface is owner-scoped (product / customer / order / etc.). Calling this action always returns:/metafields / /api/v1/metafields) for non-cart writes today.
Buyer Journey Intercept
Block the customer from advancing to payment until your condition is met — a callback-based interceptor that runs every time the buyer clicks Place order.BUYER_JOURNEY_INTERCEPT_REQUEST with a
requestId whenever the buyer clicks Place order; the SDK runs your
callback and responds via BUYER_JOURNEY_INTERCEPT_RESPONSE with the
same requestId. The callback can be async — the host waits for the
response before proceeding.
Result shape:
| Field | Type | Description |
|---|---|---|
behavior | 'allow' | 'block' | Required |
reason | string | Shown to the customer when block |
perform | function | Runs after allow — useful for side-effects |
APP_BRIDGE_RESIZE
Tell the host to resize your iframe to fit content. Use aResizeObserver to call this automatically.
[60, 800] pixels. This is a fire-and-forget message — no response.
Available Targets
Wire format below matches what you ship in your app’sextensions.checkoutExtensions[] array. Targets prefixed with checkout- are the canonical LaunchMyStore slot names; targets prefixed with purchase.checkout.* are the dot-style alias accepted for ecosystem portability.
| Target | Renders | Customer-facing use cases |
|---|---|---|
checkout-contact-after | After email/login row | Delivery notes, account-creation prompts, newsletter signups |
checkout-shipping-after | After address form | Address validation, location-based offers |
checkout-payment-before | Above payment methods | Trust badges, payment-method explainers |
checkout-payment-after | Below payment methods | ”Why you can trust us”, security seals |
checkout-order-summary-before | Top of order summary | Coupon helpers, promo banners |
checkout-order-summary-after | Bottom of order summary | Live savings calculators, loyalty point preview |
purchase.checkout.block.render | Custom block placement | General-purpose surfaces |
purchase.thank-you.block.render | Order status page right after checkout (first visit only) | Post-purchase upsells, review prompts |
purchase.order-status.block.render | Order status page, every visit | Delivery ETA widgets, returns initiators, FAQ |
purchase.thank-you.cart-line-list.render-after | After the order summary line list (first visit only) | Per-item upsells |
purchase.order-status.cart-line-list.render-after | After the order summary line list, every visit | Warranty registration, reorder buttons |
Worked Example: Delivery Instructions Card
A complete iframe that asks the customer for delivery instructions and saves them viaORDER_NOTE_SET.
app.json:
Persistence Guarantees
Two important things to understand about what survives into the placed order:ORDER_NOTE_SETsurvives. The note is stored on the cart, included in the order payload at placement, and saved to the order’snotefield.COUPON_APPLY_REQUESTis best-effort. It fills the host’s coupon input — the customer still has to click Apply, and the merchant’s coupon rules (eligibility, expiry, min-cart) still apply. There is no way for an extension to forcibly apply a coupon that the host would reject.TOAST_SHOWis ephemeral. Toasts vanish on page reload.
Security Notes
- The checkout host listens for
window.messageevents withtype: 'APP_BRIDGE_ACTION'. Origin is not currently checked because extension iframes are same-origin under/extensions/.... Do not rely on the message channel for authentication — the host treats every same-origin frame as trusted. - Customer PII (full address, phone, payment details) is not exposed via App Bridge. Only the email the customer has already typed is available via
CUSTOMER_GET. If you need more, your extension must collect it directly from the customer. - Iframes use
sandbox="allow-scripts allow-forms allow-popups allow-same-origin". Top-level navigation is blocked; popups open in a new tab.
Troubleshooting
MydispatchAndWait always times out.
The host probably isn’t mounted yet. The broker is registered when the checkout page mounts, so it’s not listening immediately on load. Wait for DOMContentLoaded plus a frame before dispatching, or retry once on timeout.
COUPON_APPLY_REQUEST returns ok: true but nothing happens.
The host puts the code into the input box but does not automatically click Apply — that’s by design so merchant coupon rules run normally. If your extension wants to provide a smoother flow, hold the code yourself and surface a clear “click Apply to use this code” affordance.
Iframe height won’t change past 800px.
The host clamps APP_BRIDGE_RESIZE to [60, 800]. Split content across multiple slots or use internal scrolling if you need more vertical space.
My iframe shows but I never get any responses.
Check the iframe’s sandbox attribute. The host requires allow-scripts and allow-same-origin; otherwise postMessage and window.parent access are blocked. The default checkout extension slot already passes the correct sandbox flags.