Skip to main content

Customer Account Extensions

Customer-account extensions are iframes injected into the customer’s account pages (/account, /orders/[orderId], /account/profile). Use them for order tracking widgets, loyalty balances, support chat panels, and similar customer-facing surfaces. These are distinct from admin extensions (which target the merchant admin UI) and from checkout extensions (which target the unauthenticated checkout flow). They run for a logged-in customer with the customer’s session.

Targets

All four targets have a slot mounted in the storefront account pages. dashboard.block.render renders above the page header on /account; order-list.block.render renders between the header and the order history list on the same page; profile.block.render renders above the “Profile” heading on /account/profile; order-status.block.render renders on the per-order page. The <CustomerAccountExtensionSlot target="..." /> component renders one iframe per registered extension at the given target. It requires the customer’s session token as bearerToken; without it the slot API returns 401 and the slot renders empty (correct secure default — extension enumeration is gated behind authentication).

Manifest

How the iframe is rendered

Each registered extension is rendered as a plain iframe pointed at its resolved url:
A few things to design around:
  • No App Bridge host on this surface. Unlike admin extensions, the customer-account slot does not run an App Bridge postMessage host, and it does not push any context object into the iframe. There is no useApi() / EXTENSION_CONTEXT wiring on the storefront side.
  • No page context is injected into the URL. The slot mounts with the store’s domainSlug and the customer’s bearer token only — the host does not append orderId / customerId query parameters to your iframe url. If your extension needs to know which order or customer it’s rendering for, fetch that from your own backend using the customer session token (see Security), or derive it from the page your block targets.
  • Fixed height. The iframe renders at a fixed 120px height. Keep your block compact, or scroll within it.

Discovery

The storefront exposes:
which returns the registered extensions plus their absolutized URLs. The slot component fetches this on mount and renders one iframe per extension. The storefront route resolves extensions from two sources and merges them: Behind the storefront route, the platform resolves the first source via:
This endpoint authenticates as the customer (not the merchant) and only returns extensions belonging to apps that are actively installed on that store and not suspended. A merchant admin token is rejected.
Extensions registered through the dev portal now render. Earlier builds had no backend resolver for this category, so only on-disk manifests were ever returned and portal-registered customer-account extensions stayed invisible with no error. If you registered one previously and never saw it, no manifest change is required — it resolves on its own.

Security

  • The iframe is sandboxed with allow-scripts allow-forms allow-popups and no allow-same-origin, so it runs in an opaque origin and cannot read the parent page’s cookies or storage.
  • Enumerating a store’s installed extensions is gated behind authentication: the discovery endpoint requires the customer’s bearer token and returns 401 without it (the slot then renders nothing). The token is validated by the platform on every call — a non-OK result yields no extensions rather than falling back to the on-disk manifests.
  • The bearer token the slot is mounted with is the customer’s session credential. If your iframe needs to call an API on behalf of the customer, your own backend must independently verify that credential — never trust ids passed to you in the clear.