Skip to main content

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

Calls to unsupported actions return no response — the SDK call will reject with a timeout after 10 seconds. Design your extension to gracefully degrade if a non-checkout action fails.

Initializing

If your extension iframe loads from a URL the merchant configured in app.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.
Use this to detect whether your extension is running inside the checkout host or some other surface, and feature-flag accordingly.

TOAST_SHOW

Show a toast notification rendered by the checkout’s toast system.
Response: { ok: true }

CART_GET

Read the current cart contents.
The 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.
Refresh by polling — there is no push notification today. A 2-3 second interval is reasonable.

CUSTOMER_GET

Read the email the customer has typed at checkout. No PII beyond what the customer has already entered into the host.
If the customer hasn’t typed an email yet, email is an empty string. The customer is not necessarily logged in; do not assume this maps to a Customers row.

CURRENCY_GET

Identical to 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.
Like 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.
No PII beyond what the customer has already entered into the host.

LOCALIZATION_GET

Country, language, and currency picked by the customer (or inherited from the store’s auto-detected locale).
Use this to switch between localised marketing copy or hide an extension entirely in unsupported regions.

SHIPPING_ADDRESS_GET

The customer’s selected delivery address. Returns address: null until they pick / type one.

BILLING_ADDRESS_GET

LaunchMyStore checkout does not split shipping/billing — this returns the same address 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”.
A single delivery group is returned today; future multi-shipment support will expand this into one group per shipment.

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.
The same map is persisted onto the order at placement and queryable in Liquid via order.attributes.

ORDER_NOTE_SET

Write a delivery note / order instruction. Persisted to the cart’s note field and saved on the order when it’s placed.
Pass an empty string to clear the note. The host stores the note on the cart and includes it in the order payload at placement.

COUPON_APPLY_REQUEST

Forward a coupon code to the host’s coupon input.
The host fills its coupon input field with 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 the Cart 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.
Response:
Internally the host applies the add / update-quantity / remove mutation to the cart and re-fetches the cart envelope. When 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.
Adding a code fills the host’s coupon input and runs the merchant’s existing 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.
The note is stored on the cart and persisted to the order’s note field at placement.

ATTRIBUTE_CHANGE

Set or remove a cart-level attribute (key/value pair stored on the order).
Attributes are stored on the cart slice and persisted with the order so they’re queryable in Liquid templates and admin order detail pages.

GIFT_CARD_CHANGE

Apply or remove a gift card on the checkout. Both operations are live — they call the same apply/remove path the native checkout gift-card field uses, so the order summary and totals update accordingly.
The SDK type union is 'addGiftCard' | 'removeGiftCard'. (The host also accepts the legacy 'applyGiftCard' alias over the raw GIFT_CARD_CHANGE action, but the typed SDK helper only allows addGiftCard.) Read applied cards back with APPLIED_GIFT_CARDS_GET (returns each card’s code, amountApplied, balanceAfter, lastCharacters).

SHIPPING_ADDRESS_CHANGE

Update the customer’s shipping address. Only updateShippingAddress is supported today — there is no add/remove distinction because checkout holds exactly one address at a time.
There is no cart.applyShippingAddressChange() helper on the SDK Cart object. Dispatch the raw SHIPPING_ADDRESS_CHANGE action directly with dispatchAndWait (the host wires it):
The host merges incoming fields with the existing address (only non-empty values overwrite). After the write, cart verification re-runs so shipping options and totals refresh on the next read. Error responses:
  • 'address required' — no address object passed
  • 'address mutation not available' — the host page hasn’t wired the writer (e.g. read-only screens)
  • 'unknown address change type'type is not updateShippingAddress

METAFIELD_CHANGE

Write or remove a cart-scoped metafield. Updates persist through the /api/apps/cart-metafields/* endpoints and are reflected in CART_GET’s metafields array, so an extension can attach data (gift message, engraving text, etc.) that follows the cart through to the order.
namespace and key are required; an unknown type returns unsupported METAFIELD_CHANGE type "<x>". For owner-scoped metafields (product / customer / order) use the REST endpoints (/metafields / /api/v1/metafields) instead.

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.
The host dispatches 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:

APP_BRIDGE_RESIZE

Tell the host to resize your iframe to fit content. Use a ResizeObserver to call this automatically.
The host clamps the height to [60, 2000] pixels. This is a fire-and-forget message — no response.

Available Targets

Wire format below matches what you ship in your app’s extensions.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.

Worked Example: Delivery Instructions Card

A complete iframe that asks the customer for delivery instructions and saves them via ORDER_NOTE_SET.
Register the manifest in your app.json:

Persistence Guarantees

Two important things to understand about what survives into the placed order:
  • ORDER_NOTE_SET survives. The note is stored on the cart, included in the order payload at placement, and saved to the order’s note field.
  • COUPON_APPLY_REQUEST is 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_SHOW is ephemeral. Toasts vanish on page reload.
Function-based price changes (cart_transform, discount, shipping_rate, payment_customization, delivery_customization, order_validation) are separate from App Bridge — they’re declarative WASM functions that run at cart verification and order placement. Use those for guaranteed cart mutations; use App Bridge actions for opt-in customer interaction.

Security Notes

  • The checkout host answers only iframes it rendered into a checkout extension slot — the message’s source window is matched against the slot iframes by identity. Messages from any other frame on the page (an app-embed script’s iframe, a chat widget, the top window itself) are silently ignored. This also means you can’t exercise the bridge from the browser devtools console at the top window — post from inside an extension iframe.
  • 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

My dispatchAndWait 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. Also confirm your code runs inside a checkout extension slot iframe — the host ignores messages from any other frame (see Security Notes). 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 2000px. The host clamps APP_BRIDGE_RESIZE to [60, 2000]. 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.