POS Extensions
POS extensions let your app inject UI into the LaunchMyStore Point-of-Sale (POS) app — the in-person sales surface staff use at a counter. Like admin blocks, POS extensions declare sandboxed iframes at named slots inside the POS UI.All six targets below render in the POS app. Each one is mounted at
a real slot in the register UI — see the Targets table for
exactly where. Extensions declared at any other
pos. target are still
installed and returned by the discovery API, but nothing mounts them.POS extensions share the manifest shape and install pipeline with
admin blocks. The only difference is the target prefix
(
pos.<surface>.<placement>.render) and a flag the host uses to
branch rendering between admin and POS surfaces.When to use POS extensions
Use a POS extension when staff at the register need information or actions that aren’t part of the default POS flow:- Look up a loyalty member by phone and apply their tier discount
- Check warehouse stock from a partner system before promising backorder
- Print a custom gift receipt with a per-item warranty card
- Show recent purchases for the current customer
Manifest
POS extensions live underextensions.posExtensions in your app.json.
The install endpoint also accepts entries in this array; the local
manifest helper merges them with adminExtensions and tags POS entries
with type: 'pos_extension'.
Manifest fields
string
required
Unique extension handle within this app. Used as the iframe id.
string
required
POS injection point — must start with
pos.. The pos. prefix is what
distinguishes a POS extension from an admin block, and it is preserved
verbatim end to end (the API’s only target rewrite is treating a
trailing .block.render as equivalent to .render), so a POS target
can never match an admin slot or vice versa. See Targets
below.string
Tab/panel label shown in the POS UI. Defaults to the extension
name
or app name when omitted.string
Absolute URL the iframe loads. Relative URLs (e.g.
/extensions/.../tool.html)
are absolutized against the LaunchMyStore origin before the response is
sent — POS clients running on a different host resolve them
correctly.string
Small icon (SVG/PNG) shown next to the title in the POS tab strip.
Override for the tab label when it appears in a list/nav strip. Falls
back to
title when omitted.object
Free-form permission flags forwarded to the host. Consumers (POS app)
decide which gates to enforce.
Targets
All six targets below are mounted by the POS app.resourceId /
resourceType query params are passed only where a resource is in scope —
the three “detail” targets. The other three are ambient: they render with
the store context but no resource id.
The three modal-hosted targets mount only while their modal is open, and
unmount when it closes — do not rely on a persistent iframe across
resources.
Targets are validated as “starts with
pos.” at the API layer, so a
custom pos.* target installs cleanly and is returned by the discovery
API — but nothing renders it. Only the six targets above have a host
slot.
Iframe contract
POS extensions follow the same iframe contract as Admin Blocks:1
Host fetches the extension list
The POS host calls
GET /api/apps/admin-extensions?target=pos.checkout.block.render&domainSlug=…
with the merchant bearer token and receives the merged backend +
local-manifest list for that exact target. Because the query is
already target-scoped, no additional POS/admin separation is needed.2
Host renders an iframe per extension
The host creates a sandboxed
<iframe> for each entry with
src = ext.url (absolutized against the LaunchMyStore origin when
relative), extensionId = ext.id in the query string, and the
target-defined permission flags.3
Iframe initializes App Bridge
Inside the iframe, call
createApp({ apiKey, host }) from
@launchmystore/app-bridge to begin posting APP_BRIDGE_ACTION
messages to the host (dispatch, dispatchAndWait, subscribe,
getSessionToken). See App Bridge.4
Resize negotiation
Send
{ type: 'APP_BRIDGE_RESIZE', extensionId, height } whenever
the iframe’s content height changes. The extensionId MUST match
the id the host passed in via ?extensionId= — otherwise the
iframe stays at the 200px default and looks empty.Install pipeline
POS extensions install through the same endpoint as every other extension.POST /api/apps/install-extensions is a server-to-server
endpoint gated by the platform’s internal API key — it is normally invoked
by the backend as part of the app install flow, not called directly from a
browser with a merchant token. (The only browser-accepted variant is a
fromCatalog: true catalog copy, which additionally requires the merchant
to own the target store.)
- Persists the array into
app.jsonunderextensions.posExtensions. - Invalidates the local manifest cache so the next read picks up the new entries within the 60s TTL.
- Returns
installed.posExtensions = <count>in the response.
Discovery API
POS hosts call the same admin-extensions endpoint with apos. target:
Security
- All iframes load over HTTPS in production and are sandboxed. Your extension is served from your own origin, so it is cross-origin to the POS host and cannot read the host’s cookies, storage or DOM.
- App Bridge
postMessageis gated by the host’sallowedOriginslist — only registered app origins can dispatch actions. - Relative
urlvalues are absolutized against the LaunchMyStore storefront origin before being returned — POS clients calling cross-origin still resolve the iframe correctly.
See also
Admin Blocks
Same iframe contract for merchant admin pages.
App Bridge
Iframe-to-host messaging SDK.
App Manifest
Every field in
app.json.App Bridge Actions
Resource picker (11 types including
customer and product) and all
other actions.