# Getting started

## Requirements

- A Pancake account that is a member of the shop and has the **configuration management** permission (the owner or someone granted it).
- Node.js 22 or later (24 recommended).
- Basic Preact knowledge (or React for iframe pages). If you vibe-code, connect your AI tool to the MCP server as described in [Build with AI](/developers/apps/en/ai-guide), or point it at `/developers/apps/llms-full.txt`.

## 1. Create a project

```bash
npx create-pos-app my-app
cd my-app
npm install
```

Default layout:

```text
my-app/
├─ pos-app.json            # manifest: permissions, external domains, extensions
├─ extensions/             # code that runs in the worker (Worker UI or headless)
│  ├─ order-block.tsx
│  └─ push-erp.ts
├─ page/                   # iframe page (optional), Vite + React
│  ├─ index.html
│  ├─ tsconfig.json        # separate TypeScript config for React
│  └─ src/main.tsx
├─ public/                 # images for pos-image (optional)
├─ AGENTS.md               # rules for AI tools, generated by the template
├─ eslint.config.js        # code rules, editors show problems as you type
├─ package.json
└─ tsconfig.json
```

The template declares these packages, installed by `npm install`:

- `@pancake-pos/ui-extensions`: the SDK, with types for the `pos` object and the `pos-*` elements.
- `@pancake-pos/ui`: theme and POS-styled components for iframe pages.
- `@pancake-pos/app-cli`: the `pos-app` command.
- `@pancake-pos/eslint-plugin`: code rules, shared with `pos-app check`.

Package details, TypeScript setup and the full CLI reference: [SDK & CLI](/developers/apps/en/sdk).

## 2. Declare extensions

Example: add a block to the order form and an item to the order action menu:

```json
{
  "handle": "my-app",
  "name": "My app",
  "version": "0.1.0",
  "api_version": "2026-01",
  "access": { "methods": ["GET", "PUT"] },
  "extensions": [
    { "target": "order.details.block", "id": "order-block", "module": "extensions/order-block.tsx" },
    {
      "target": "order.details.action",
      "id": "push-erp",
      "label": "Push to ERP",
      "presentation": "headless",
      "module": "extensions/push-erp.ts"
    }
  ]
}
```

See every field in [Manifest](/developers/apps/en/manifest) and every target in [Extension points](/developers/apps/en/targets).

## 3. Try it inside POS

In POS, go to **Settings › Advanced › Custom apps**, click **Connect CLI** › **Create token** and run the command it shows. The command is shown only once; the same dialog lists the shop's active tokens so you can revoke the ones no longer in use (screenshots and details: [Manage apps › Connect the CLI](/developers/apps/en/manage-apps#connect-the-cli)). It looks like:

```bash
npx @pancake-pos/app-cli login --shop 123 --token <token>
```

Then run:

```bash
npx pos-app dev              # watch and build, serve at http://localhost:5174
```

On the same management page, click **Development mode** and enter `http://localhost:5174` (see [Manage apps › Development mode](/developers/apps/en/manage-apps#development-mode)). While it is on, POS loads the app from `localhost` **in your browser only**; everyone else in the shop keeps using the active version. When you edit code, POS reloads the extensions without reloading the page. Use Chrome or Edge: Safari may block an `https` page from loading `http://localhost`.

In development mode, `pos.api` can call the Open API only after the app has been uploaded at least once (`npx pos-app deploy`, no activation needed), and it uses the `access` of the latest uploaded version. Deploy again after changing `access`. After each such deploy, reload the POS page so POS gets a new token.

Development mode runs against the real data of the open shop, so try a test shop first. To see the app as a staff member with narrow permissions, deploy and activate it on a test shop and sign in with that account.

While you write code, an editor with the ESLint extension shows problems as you type. Run `npx pos-app check` to validate the manifest as well.

## 4. Build and upload

```bash
npx pos-app deploy
```

This command:

1. Runs `pos-app check`: validates the manifest and catches common code mistakes (see [SDK & CLI](/developers/apps/en/sdk#pos-app-check)).
2. Builds each module in `extensions/` into a standalone ES module and builds `page/` with Vite.
3. Runs final checks: valid targets, existing files, sizes within the [limits](/developers/apps/en/overview#limits).
4. Uploads and creates a **new version** in the inactive state, then prints the version id.

## 5. Activate

On the **Custom apps** page, a shop admin:

1. Clicks **Versions** on the app's row (for a new app: **Review & activate**), then **Activate** on the uploaded version.
2. Reviews the data access (`access`), external servers (`network.allowed_origins`) and placements the app requests, then clicks **Activate**. Users see the extensions on their next page load.

![The access review shown before activating a version](/static/developers/apps/manage/en/review.webp)

By default every shop member sees the app; limiting it by department is not yet available on the management page. Going back to an older version, turning the app off and reading the error log: [Manage apps in POS](/developers/apps/en/manage-apps).

## Next steps

- [Manage apps in POS](/developers/apps/en/manage-apps): activate, turn on and off, read errors.
- [Build with AI](/developers/apps/en/ai-guide): the MCP server and sample prompts.
- [Examples](/developers/apps/en/examples): a complete sample app.
- [Runtime API](/developers/apps/en/runtime-api): the `pos` object.
- [UI components](/developers/apps/en/components): the `pos-*` elements.
