Reference

Manifest (pos-app.json)

Declare an app: data access, allowed external domains and the list of extensions.

View Markdown

pos-app.json lives at the project root. POS reads it on upload to learn which permissions the app needs, which domains it may call and where it attaches.

Full example

JSON
{
  "handle": "debt-reconciliation",
  "name": "Debt reconciliation",
  "description": "Checks customer debt when creating orders and reconciles at the end of the day.",
  "version": "1.3.0",
  "api_version": "2026-01",
  "access": {
    "methods": ["GET", "POST", "PUT"],
    "warehouse_ids": null,
    "hidden_order_fields": ["bill_phone_number"]
  },
  "network": {
    "allowed_origins": ["https://erp.example-customer.com"]
  },
  "extensions": [
    { "target": "nav.item", "id": "nav", "label": "Reconciliation", "page": "home" },
    { "target": "app.page", "id": "home", "title": "Debt reconciliation", "render": "iframe", "entry": "page/index.html" },
    {
      "target": "order.details.action",
      "id": "push-erp",
      "label": "Push to ERP",
      "presentation": "headless",
      "module": "extensions/push-erp.ts",
      "conditions": { "status": [1, 2] }
    },
    {
      "target": "order.details.action",
      "id": "warranty",
      "label": "Create warranty card",
      "presentation": "modal",
      "title": "Warranty card",
      "module": "extensions/warranty-modal.tsx",
      "should_render": "extensions/warranty-should-render.ts"
    },
    { "target": "order.details.block", "id": "debt", "module": "extensions/debt-block.tsx" },
    {
      "target": "purchase.index.action",
      "id": "push-purchase-erp",
      "label": "Push to ERP",
      "presentation": "headless",
      "module": "extensions/push-purchase-erp.ts",
      "conditions": { "status": [1] }
    },
    {
      "target": "order.before_submit",
      "id": "check-debt",
      "module": "extensions/check-debt.ts",
      "timeout_ms": 2000,
      "on_error": "allow"
    }
  ]
}

Top-level fields

Field Type Required Description
handle string Yes App identifier within the shop, matching ^[a-z0-9][a-z0-9-]{1,39}$. Used in the app page URL. Changing handle makes the next upload create a separate new app.
name string Yes Display name, up to 60 characters.
description string No Short description, up to 300 characters. Shown to the admin when activating.
version string Yes Semver, for example 1.3.0. Every upload must use a version that does not exist yet.
api_version string Yes The POS contract version (YYYY-MM). It determines the available targets, components and context schemas.
access object No Limits on the data the app can use. See access.
network object No External domains the app may call. See network.
extensions array Yes Up to 30 extensions. See extensions.

access

Additional limits on top of the current user's permissions. An app token never has more permissions than the user; access only narrows them further.

Field Type Default Description
methods string[] ["GET"] Allowed HTTP methods: GET, POST, PUT, DELETE. When omitted the app is read-only.
warehouse_ids uuid[] no limit Only operate on these warehouses. Applies to orders, purchases, exports, warehouse transfers and stock by warehouse.
order_sources string[] no limit Only see orders from these sources.
hidden_order_sources string[] none hidden Hide orders from these sources.
order_statuses int[] no limit Only see orders in these statuses.
order_since datetime (ISO 8601) no limit Only see orders created from this time on.
hidden_order_fields string[] none hidden Remove these fields from order data returned to the app, including context data.
product_categories int[] no limit Only see products in these categories.
product_brands int[] no limit Only see products of these brands.

null or an omitted field means no additional limit (except methods). The limits that apply are the access of the active version.

network

Field Type Description
allowed_origins string[] https:// origins the app may call with fetch, up to 10, no wildcards. The POS Open API is always allowed and does not need to be listed. Also applies to pos-image sources.

Requests to origins not on the list are blocked by the browser. External servers must enable CORS for the app's sandbox domain (see Data access).

extensions

Common fields

Field Type Required Description
target string Yes Where the extension attaches. See Extension points.
id string Yes Unique within the app, matching ^[a-z0-9-]{1,40}$.
module string Depends on target Source file path (.ts, .tsx, .js, .jsx). The CLI builds it into a standalone ES module. Required for Worker UI and headless targets.

Action fields

Apply to *.details.action, *.index.action and *.index.selection-action of every resource.

Field Type Required Description
label string Yes Menu item label, up to 40 characters.
presentation string Yes headless, modal, fullscreen or navigate. See action types.
title string No Modal title (modal, fullscreen). Defaults to label.
page string With navigate id of the app.page extension to open.
path string No Path inside the page for navigate, for example /returns.
conditions object No When to show the menu item, evaluated by POS. See conditions.
should_render string No A module returning true/false to decide whether to show the item. Use it when conditions is not enough.

Block fields

Apply to *.details.block.

Field Type Required Description
conditions object No When to show the block.
should_render string No Same as for actions.

Page and menu fields

Target Field Description
app.page title (required) Page title.
app.page render (required) iframe or worker (Worker UI pages are planned for phase 2).
app.page entry The page's HTML file in the bundle, with render: "iframe". Declare entry or url.
app.page url Self-hosted page: an https address on your own server that POS embeds instead of a file in the bundle. No credentials, query or #; no Pancake domains, localhost or IP addresses. Changing url requires a new version. See Self-hosted pages.
app.page module Required when render: "worker".
nav.item label (required) Label in the POS menu, up to 30 characters.
nav.item page (required) id of the app.page extension.

Hook fields

Apply to *.before_submit and *.after_save.

Field Type Default Description
timeout_ms int 2000 before_submit only, at most 5000.
on_error string allow before_submit only: allow lets the save continue when the hook fails or times out, block stops it.

conditions

Keys are combined with AND; values in an array within one key are combined with OR. POS evaluates conditions while rendering, so menus never slow down.

Key Type Resources True when
mode string[] All (detail form targets) create (new, unsaved record) or edit (existing record).
status int[] order, purchase, export, warehouse_transfer The record status is in the list (status codes as in the Open API).
warehouse_ids uuid[] order, purchase, export, warehouse_transfer The record's warehouse is in the list; for a transfer, either the source or the destination warehouse.
tags_any (int | string)[] order, product, purchase, export The record has at least one of the listed tags (tag IDs).
type int[] export The export type is in the list.
category_ids int[] product The product belongs to at least one of the listed categories.
order_sources string[] order The order source is in the list.
has_customer boolean order The order has / does not have a customer attached.

Using a key that does not apply to the target's resource is a validation error (invalid_condition).

Example: only show for existing orders in status 1 or 2 that carry the VIP tag (ID 7):

JSON
{ "mode": ["edit"], "status": [1, 2], "tags_any": [7] }

Example: only show for purchases that have been received (status 1) into one specific warehouse:

JSON
{ "status": [1], "warehouse_ids": ["a52f…"] }

Manifest validation errors

Code Cause
invalid_handle handle has an invalid format.
invalid_field A field has an invalid value, for example it is too long, access has an unknown key, or access.methods is invalid.
duplicate_version version already exists.
unsupported_api_version api_version is no longer supported or does not exist.
unknown_target target does not exist in the declared api_version.
missing_field A field required by the target is missing.
module_not_found The module/entry/should_render file cannot be found.
bundle_too_large A module or the bundle exceeds the size limits.
storage_quota_exceeded The shop's total app storage would exceed 500 MB.
invalid_bundle The uploaded file is not a valid .zip package, or contains an unsafe file path.
upload_failed The bundle could not be stored; upload again.
invalid_origin An allowed_origins entry is not https:// or uses a wildcard.
invalid_url The app.page url is not https with a domain name, contains credentials, a query or #, or belongs to a Pancake domain.
invalid_condition A conditions key does not apply to the target's resource.