Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.launchmystore.io/llms.txt

Use this file to discover all available pages before exploring further.

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 render as sandboxed iframes at named slots inside the POS UI.
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
If your app only needs to add screens to the merchant admin (no register flow), use Admin Blocks or Admin Actions instead.

Manifest

POS extensions live under extensions.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'.
{
  "handle": "loyalty-tools",
  "name": "Loyalty Tools for POS",
  "version": "1.0.0",
  "extensions": {
    "posExtensions": [
      {
        "handle": "loyalty-lookup",
        "target": "pos.checkout.block.render",
        "title": "Loyalty Lookup",
        "url": "https://my-app.example.com/pos/loyalty-lookup",
        "iconUrl": "https://my-app.example.com/icons/loyalty.svg",
        "permissions": { "read_customers": true }
      }
    ]
  }
}

Manifest fields

handle
string
required
Unique extension handle within this app. Used as the iframe id.
target
string
required
POS injection point — must start with pos.. The host uses the pos. prefix to distinguish POS extensions from admin blocks (every entry whose target starts with pos. is tagged type: 'pos_extension' on the wire). See Targets below.
title
string
Tab/panel label shown in the POS UI. Defaults to the extension name or app name when omitted.
url
string
Absolute URL the iframe loads. Relative URLs (e.g. /extensions/.../tool.html) are absolutized against the CustomerLMS origin before the response is sent — POS clients running on a different host resolve them correctly.
iconUrl
string
Small icon (SVG/PNG) shown next to the title in the POS tab strip.
navigationLabel
string
Override for the tab label when it appears in a list/nav strip. Falls back to title when omitted.
permissions
object
Free-form permission flags forwarded to the host. Consumers (POS app) decide which gates to enforce.

Targets

The full target catalog is being finalized as the POS app rolls out. Targets are validated as “starts with pos.” at the API layer — any target your host knows about will work, but unrecognized targets simply don’t surface. Treat the list below as authoritative for shipping today; new placements will be added here as the POS app ships them.
TargetSurfaceWhen it renders
pos.home.block.renderPOS home screenLoaded into a tile on the staff home screen.
pos.checkout.block.renderActive sale screenSidebar block visible during the checkout flow.
pos.customer.details.block.renderCustomer detail cardBlock on the in-POS customer view (during a sale).
pos.product.details.block.renderProduct quick viewBlock on the product quick-view modal in POS.
pos.cart.block.renderCart drawerBelow the line items in the cart drawer.
pos.order.action.renderCompleted orderAction button on a finalized order receipt screen.
Custom targets are allowed — any string starting with pos. will be returned by the admin-extensions API. The POS host is free to mount unknown targets in a generic slot or ignore them.

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 a bearer token, receives the merged backend + local-manifest list, and filters by type === 'pos_extension' if it wants to keep POS UI separate.
2

Host renders an iframe per extension

The host creates a sandboxed <iframe> for each entry with src = ext.url (absolutized against the CustomerLMS 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.
// Inside your POS extension iframe
import { createApp } from '@launchmystore/app-bridge';

const params = new URLSearchParams(window.location.search);
const app = createApp({
  apiKey: 'your-app-api-key',
  host: params.get('host'),
});

// Resize the iframe to fit content
const ro = new ResizeObserver(() => {
  window.parent.postMessage(
    {
      type: 'APP_BRIDGE_RESIZE',
      extensionId: params.get('extensionId'),
      height: document.body.scrollHeight,
    },
    '*'
  );
});
ro.observe(document.body);

// Look up a customer via App Bridge resource picker
async function pickCustomer() {
  const result = await app.dispatchAndWait('RESOURCE_PICKER_OPEN', {
    type: 'customer',
    multiple: false,
  });
  return result.selection?.[0];
}

Install pipeline

POS extensions install through the same endpoint as every other extension:
curl -X POST "https://store.launchmystore.io/api/apps/install-extensions" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -d '{
    "domainSlug": "raja337276",
    "appHandle": "loyalty-tools",
    "extensions": {
      "posExtensions": [
        {
          "handle": "loyalty-lookup",
          "target": "pos.checkout.block.render",
          "title": "Loyalty Lookup",
          "url": "https://my-app.example.com/pos/loyalty-lookup"
        }
      ]
    }
  }'
The installer:
  1. Persists the array into app.json under extensions.posExtensions.
  2. Invalidates the local manifest cache so the next read picks up the new entries within the 60s TTL.
  3. Returns installed.posExtensions = <count> in the response.

Discovery API

POS hosts call the same admin-extensions endpoint with a pos. target:
GET /api/apps/admin-extensions?target=pos.checkout.block.render&domainSlug=mystore
Authorization: Bearer <merchant-jwt>
Response (truncated):
{
  "extensions": [
    {
      "id": "loyalty-tools::loyalty-lookup",
      "appId": "loyalty-tools",
      "appHandle": "loyalty-tools",
      "name": "Loyalty Tools for POS",
      "handle": "loyalty-lookup",
      "target": "pos.checkout.block.render",
      "type": "pos_extension",
      "title": "Loyalty Lookup",
      "url": "https://store.launchmystore.io/extensions/mystore/loyalty-tools/...",
      "appUrl": "https://store.launchmystore.io/extensions/mystore/loyalty-tools/...",
      "iconUrl": "https://my-app.example.com/icons/loyalty.svg",
      "permissions": { "read_customers": true }
    }
  ]
}
The host can filter to POS-only by passing &type=pos_extension.

Security

The admin-extensions API requires a bearer token. Unauthenticated requests get 401 Missing bearer token with an empty extension list. This prevents leaking which apps a merchant has installed.
  • All iframes load over HTTPS in production. The POS host disables allow-same-origin so iframes cannot read the host’s cookies.
  • App Bridge postMessage is gated by the host’s allowedOrigins list — only registered app origins can dispatch actions.
  • Relative url values are absolutized against PUBLIC_CUSTOMERLMS_ORIGIN (or the request host) 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.

Resource Picker

11 picker types including customer and product.