Reference

SDK & CLI

SDK packages, how the SDK and the POS runtime split the work, TypeScript setup, defineExtension and the pos-app command.

View Markdown

Packages

Package Used in Role
@pancake-pos/ui-extensions extensions/, page/ Types for pos and pos-* elements, the defineExtension helper, the client for iframe pages (/page)
@pancake-pos/ui page/ Theme and POS-styled components for iframe pages
@pancake-pos/app-cli Dev machine, CI The pos-app command: sign in, try in POS, check, build, upload
@pancake-pos/eslint-plugin Editor, pos-app check Code rules for apps; editors show problems as you type
create-pos-app Once Creates a starter project with the packages above and an AGENTS.md

Package versions follow api_version: package 2026.1.x is for "api_version": "2026-01". The CLI reports an error when they do not match.

SDK and the POS runtime

Code running in the sandbox has two parts, provided by two different parties:

Text
Worker inside the app sandbox
┌─────────────────────────────────────────────────────┐
│ App bundle (built and uploaded by the shop)         │
│   extension modules + Preact or plain JS            │
├─────────────────────────────────────────────────────┤
│ POS runtime (provided by POS, not in the bundle)    │
│   minimal DOM · pos-* elements · global pos object  │
│   postMessage channel · app token · Open API calls  │
└─────────────────────────────────────────────────────┘
  • The POS runtime is loaded into the worker before the app's modules. Because it is not part of the bundle, POS can ship security fixes or improvements without apps redeploying, and app modules stay small.
  • @pancake-pos/ui-extensions mostly provides types and helpers in extensions/; it adds almost no code to the bundle.
  • Iframe pages have no preloaded runtime: the app imports connect() from @pancake-pos/ui-extensions/page, and this small client is bundled with the page.

What the SDK and runtime handle for you

Concern Details
Messaging postMessage between the sandbox and POS, sender verification, commands and events in both directions
Authentication Fetches the app token, renews it before it expires, refreshes and retries once on 401
Open API calls pos.api.*, adds the Authorization header, blocks methods outside access.methods, turns errors into PosApiError
Worker UI Minimal DOM, pos-* element definitions, batching changes sent to POS, delivering POS events to the worker
Context Updates pos.data, emits data-change events
Iframe pages Syncs the URL with POS, setTitle, setPrimaryAction
Development mode Loads the bundle from localhost, reloads extensions and pages after every build

Installation

Projects created with create-pos-app already have everything. To add the SDK to an existing project:

Terminal
npm install @pancake-pos/ui-extensions preact
npm install -D @pancake-pos/app-cli typescript eslint @pancake-pos/eslint-plugin
npm install @pancake-pos/ui react react-dom   # only if you have an iframe page

TypeScript

The tsconfig.json at the project root covers extensions/:

JSON
{
  "compilerOptions": {
    "target": "ES2020",
    "module": "ESNext",
    "moduleResolution": "Bundler",
    "jsx": "react-jsx",
    "jsxImportSource": "preact",
    "strict": true,
    "types": ["@pancake-pos/ui-extensions"]
  },
  "include": ["extensions"]
}
  • types declares the global pos object and the pos-* elements for JSX (properties, events, event.detail).
  • The page/ folder has its own page/tsconfig.json with "jsxImportSource": "react", generated by the template; do not merge it into the root tsconfig.
  • extensions/ supports Preact (recommended, small) and plain JS using the runtime's DOM API. React is not supported in extensions/ yet: React 18 turns array/object properties into strings and cannot listen to pos-* element events; React 19 is under evaluation. Iframe pages can use React as usual.

defineExtension

An optional helper that makes the type of data and of the return value match the target.

TypeScript
import { defineExtension } from '@pancake-pos/ui-extensions';

export default defineExtension('order.before_submit', async (data) => {
  // data: DetailsData<'order'>; must return BeforeSubmitResult
  if (data.draft.total_price > 20_000_000 && !data.draft.bill_phone_number) {
    return { ok: false, message: 'Large orders need a phone number.' };
  }
  return { ok: true };
});

defineExtension returns the function you pass in, so it is equivalent to export default async function (data).

The resource comes from the target name, so the same helper types data.draft as a PurchaseDraft in purchase.before_submit or data.records as WarehouseTransferRef[] in warehouse_transfer.index.selection-action.

Exported types: Resource, DetailsData, IndexData, SelectionData, AfterSaveInput, BeforeSubmitResult, HeadlessActionResult, PageData, the Ref/Draft pair of each resource (OrderRef, OrderDraft, ProductRef, ProductDraft, PurchaseRef, PurchaseDraft, ExportRef, ExportDraft, WarehouseTransferRef, WarehouseTransferDraft), PosApiError, PosRuntimeError.

Iframe pages

TypeScript
import { connect } from '@pancake-pos/ui-extensions/page';

const pos = await connect({ timeout: 10_000 });
  • connect() waits for POS to send the context and token; after timeout (10 seconds by default) it throws, usually because the page was opened outside POS.
  • The page runs only inside a POS iframe. Opening a sandbox URL in its own tab or window, including a popup the page opens itself, is refused (403). A popup sign-in flow must return to a page of yours outside the sandbox and report back to the app through your server.
  • The page's pos has the same API as in the worker, plus pos.page.*. See Runtime API.

CLI (pos-app)

Command Description
pos-app login --token <token> [--shop <shop_id>] Stores the CLI session on this machine. Copy the full command, token included, from the Connect CLI button on the custom apps management page (requires the Settings permission); the token only works for that shop and the CLI's own commands (upload, versions, errors, sample data; it cannot activate apps or sign in to POS), lasts 30 days and stops working when you sign out of POS or when it is revoked from the Connect CLI button
pos-app logout Revokes the CLI token, then removes the session from this machine
pos-app dev [--port 5174] [--page-url <url>] Watch and build, serve at http://localhost:<port> for POS development mode; POS reloads after every build. --page-url: a self-hosted page running on your machine (for example http://localhost:3000)
pos-app check Validates the manifest and code (see below) without uploading
pos-app build Builds into dist/
pos-app deploy [--shop <shop_id>] [--message "..."] Runs check and build, uploads a new (inactive) version
pos-app versions [--shop <shop_id>] Lists uploaded versions and the active one
pos-app mcp [--shop <shop_id>] Starts the MCP server for AI tools, see MCP

Every command accepts --json for machine-readable output (CI, scripts, the MCP server).

The pos-app command is available once @pancake-pos/app-cli is installed, so npx pos-app … works inside the project folder. Outside a project, call it by package name: npx @pancake-pos/app-cli <command>. The pos-app package on npm is not published by Pancake.

Activation and rollback happen on the custom apps page in POS, so an admin always approves permissions before an app runs.

pos-app check

Static checks that run automatically before deploy. Especially useful for AI-generated code:

  • Manifest: valid against the schema, reported with line numbers; targets exist in api_version; module/entry files exist; conditions keys are valid for the resource.
  • Code: runs the code rules with a built-in configuration; nothing to configure in the project.
  • Sizes of modules and bundles are within limits.

error findings block deploy; warning findings are only reported. Each finding has a file, line, rule id and a suggested fix (--json for CI and MCP). pos-app check ignores eslint-disable comments, so disabling a rule in code does not bypass this check.

Code rules

The rules ship in @pancake-pos/eslint-plugin. Element rules are generated from the component catalog of the exact api_version, so they always match POS.

Group Rules Level
Worker environment No window, document.querySelector, localStorage, alert…; no react, react-dom, CSS files or UI libraries imported in extensions/ error
pos-* elements Elements, properties and events exist in the catalog (with a suggestion for typos); valid enum values; valid children and slots; text only inside elements that accept text error
Data and security pos.api.* methods are listed in access.methods; literal fetch URLs and pos-image sources belong to network.allowed_origins (images may also use public/); no hardcoded shop id; no strings that look like API keys, tokens or secrets error
Behavior Modules export default a function; rendered lists have a key; input value is not set after an await; before_submit does not chain several API calls warning
Iframe pages No window.top or window.parent; no routes starting with /_pos/ error

Static checks cannot catch dynamic cases such as element names built from variables. In development mode POS also logs warnings to the console at run time.

Editor feedback

Projects created with create-pos-app include an eslint.config.js:

JavaScript
import posApp from '@pancake-pos/eslint-plugin';

// recommended reads pos-app.json for api_version, access and allowed_origins
export default [...posApp.configs.recommended];

Install the ESLint extension in your editor (VS Code, Cursor…) to see problems as you type. npx eslint . gives the same results as the code part of pos-app check.

Build output

Text
dist/
├─ pos-app.json              # manifest with module paths pointing to built files
├─ ext/
│  ├─ order-block.js         # one ES module per module, Preact included
│  └─ push-erp.js
├─ public/                   # images for pos-image, copied as is
└─ page/                     # iframe page built by Vite
   ├─ index.html
   └─ assets/...
  • Extension modules are built with esbuild, targeting ES2020, without code splitting and without imports from CDNs.
  • Iframe pages are built with Vite using the configuration that ships with the template.

MCP

pos-app mcp starts an MCP server (stdio) so AI tools can look up the docs, see the shop's real metadata, check the app, try it, read errors, deploy and report bugs to Pancake without leaving the chat.

Terminal
npx @pancake-pos/app-cli mcp --shop 123          # run in the app's project folder, or add --cwd <folder>

How to add it to Claude Code, Cursor, VS Code, Codex and Claude Desktop, the tool list and sample prompts: Build with AI › MCP.