# Bắt đầu

## Điều kiện

- Tài khoản Pancake là thành viên của shop, có quyền **quản lý cấu hình** (owner hoặc người được cấp quyền).
- Node.js 22 trở lên (khuyên dùng 24).
- Biết Preact cơ bản (hoặc React nếu làm trang iframe). Nếu vibe-code, kết nối công cụ AI với MCP server theo [Làm app với AI](/developers/apps/ai-guide), hoặc cho nó đọc file `/developers/apps/llms-full.txt`.

## 1. Tạo dự án

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

Cấu trúc mặc định:

```text
my-app/
├─ pos-app.json            # manifest: quyền, domain ngoài, danh sách extension
├─ extensions/             # code chạy trong worker (Worker UI hoặc headless)
│  ├─ order-block.tsx
│  └─ push-erp.ts
├─ page/                   # trang iframe (tuỳ chọn), Vite + React
│  ├─ index.html
│  ├─ tsconfig.json        # TypeScript riêng cho React
│  └─ src/main.tsx
├─ public/                 # ảnh cho pos-image (tuỳ chọn)
├─ AGENTS.md               # quy tắc cho công cụ AI, sinh sẵn từ template
├─ eslint.config.js        # luật kiểm tra code, editor báo lỗi khi gõ
├─ package.json
└─ tsconfig.json
```

Template khai báo sẵn các gói sau, cài bằng `npm install`:

- `@pancake-pos/ui-extensions`: SDK, khai báo type cho đối tượng `pos` và các element `pos-*`.
- `@pancake-pos/ui`: theme và component theo style POS, dùng cho trang iframe.
- `@pancake-pos/app-cli`: lệnh `pos-app`.
- `@pancake-pos/eslint-plugin`: luật kiểm tra code, dùng chung với `pos-app check`.

Chi tiết từng gói, cấu hình TypeScript và đầy đủ lệnh CLI: [SDK & CLI](/developers/apps/sdk).

## 2. Khai báo extension

Ví dụ thêm một block vào form đơn và một action vào menu thao tác đơn:

```json
{
  "handle": "my-app",
  "name": "App của tôi",
  "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": "Đẩy sang ERP",
      "presentation": "headless",
      "module": "extensions/push-erp.ts"
    }
  ]
}
```

Xem đầy đủ trường tại [Manifest](/developers/apps/manifest) và danh sách vị trí tại [Extension point](/developers/apps/targets).

## 3. Chạy thử với POS

Trong POS, vào **Cấu hình › Nâng cao › Ứng dụng tuỳ biến**, bấm **Kết nối CLI** › **Tạo token** rồi chạy lệnh hiện ra. Lệnh chỉ hiện một lần; cũng ở đó, bạn xem các token đang dùng được của shop và thu hồi token không còn dùng (ảnh và chi tiết: [Quản lý ứng dụng › Kết nối CLI](/developers/apps/manage-apps#ket-noi-cli)). Lệnh có dạng:

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

Sau đó chạy:

```bash
npx pos-app dev              # build theo dõi thay đổi, phục vụ tại http://localhost:5174
```

Trên cùng trang quản lý, bấm **Chế độ phát triển** và nhập `http://localhost:5174` (xem [Quản lý ứng dụng › Chế độ phát triển](/developers/apps/manage-apps#che-do-phat-trien)). Khi bật, POS nạp app từ `localhost` **chỉ trong trình duyệt của bạn**; người khác trong shop vẫn dùng version đang kích hoạt. Sửa code thì POS tự nạp lại extension, không phải tải lại trang. Dùng Chrome hoặc Edge: Safari có thể chặn trang `https` nạp từ `http://localhost`.

Ở chế độ phát triển, `pos.api` chỉ gọi được Open API khi app đã upload ít nhất một lần (`npx pos-app deploy`, không cần kích hoạt), và dùng `access` của version upload gần nhất. Đổi `access` thì deploy lại. Sau mỗi lần deploy như vậy, tải lại trang POS để POS lấy token mới.

Chế độ phát triển chạy với dữ liệu thật của shop đang mở, nên thử trên shop test trước. Muốn xem app với tài khoản nhân viên quyền hẹp, deploy và kích hoạt trên shop test rồi đăng nhập bằng tài khoản đó.

Trong lúc viết code, editor có extension ESLint báo lỗi ngay khi gõ. Chạy `npx pos-app check` để kiểm tra cả manifest.

## 4. Đóng gói và upload

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

Lệnh này:

1. Chạy `pos-app check`: kiểm tra manifest và các lỗi hay gặp trong code (xem [SDK & CLI](/developers/apps/sdk#pos-app-check)).
2. Build từng module trong `extensions/` thành một file ES module độc lập, build trang `page/` bằng Vite.
3. Kiểm tra lần cuối: target hợp lệ, file tồn tại, kích thước trong [giới hạn](/developers/apps/overview#gioi-han).
4. Upload và tạo **version mới** ở trạng thái chưa kích hoạt, in ra mã version.

## 5. Kích hoạt

Trên trang **Ứng dụng tuỳ biến**, quản trị viên của shop:

1. Bấm **Phiên bản** ở dòng của app (app mới: **Duyệt & kích hoạt**), rồi **Kích hoạt** ở version vừa upload.
2. Xem quyền dữ liệu (`access`), máy chủ bên ngoài (`network.allowed_origins`) và vị trí hiển thị app yêu cầu, rồi bấm **Kích hoạt**. Người dùng thấy extension ở lần tải trang kế tiếp.

![Hộp duyệt quyền trước khi kích hoạt version](/static/developers/apps/manage/vi/review.webp)

Mặc định mọi thành viên của shop đều thấy app; giới hạn theo bộ phận chưa có trên trang quản lý. Quay về version cũ, tắt app, xem nhật ký lỗi: [Quản lý ứng dụng trong POS](/developers/apps/manage-apps).

## Bước tiếp theo

- [Quản lý ứng dụng trong POS](/developers/apps/manage-apps): kích hoạt, bật tắt, xem lỗi.
- [Làm app với AI](/developers/apps/ai-guide): MCP server và câu lệnh mẫu.
- [Ví dụ](/developers/apps/examples): app mẫu hoàn chỉnh.
- [Runtime API](/developers/apps/runtime-api): đối tượng `pos`.
- [Component giao diện](/developers/apps/components): element `pos-*`.
