shopify-pos
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseShopify POS UI Extensions (2026)
Shopify POS UI 扩展(2026)
Build custom extensions that integrate directly into Shopify's Point of Sale interface on iOS and Android devices.
构建可直接集成到iOS和Android设备上Shopify销售点界面的自定义扩展。
Official References
官方参考资料
Prerequisites
前置条件
- Shopify CLI (latest)
- Shopify App with POS enabled
- Development store with POS Pro subscription
Enable POS embedding: In Partner Dashboard > App > Configuration, set "Embed app in Shopify POS" to True.
- 最新版Shopify CLI
- 已启用POS功能的Shopify应用
- 拥有POS Pro订阅的开发商店
启用POS嵌入:在合作伙伴后台 > 应用 > 配置中,将"Embed app in Shopify POS"设置为True。
Extension Architecture
扩展架构
POS UI extensions have three interconnected parts:
- Targets - Where your extension appears (tile, modal, block, menu item)
- Target APIs - Data and functionality access (Cart, Customer, Session, etc.)
- Components - Native UI building blocks (Button, Screen, List, etc.)
POS UI扩展包含三个相互关联的部分:
- 目标点位(Targets) - 扩展的展示位置(磁贴、模态框、区块、菜单项)
- 目标点位API - 数据和功能访问权限(购物车、客户、会话等)
- 组件 - 原生UI构建块(Button、Screen、List等)
Creating a POS Extension
创建POS扩展
bash
shopify app generate extension --template pos_ui --name "my-pos-extension"bash
shopify app generate extension --template pos_ui --name "my-pos-extension"Configuration (shopify.extension.toml)
配置(shopify.extension.toml)
toml
api_version = "2025-10"
[[extensions]]
type = "ui_extension"
name = "my-pos-extension"
handle = "my-pos-extension"
[[extensions.targeting]]
module = "./src/Tile.tsx"
target = "pos.home.tile.render"
[[extensions.targeting]]
module = "./src/Modal.tsx"
target = "pos.home.modal.render"toml
api_version = "2025-10"
[[extensions]]
type = "ui_extension"
name = "my-pos-extension"
handle = "my-pos-extension"
[[extensions.targeting]]
module = "./src/Tile.tsx"
target = "pos.home.tile.render"
[[extensions.targeting]]
module = "./src/Modal.tsx"
target = "pos.home.modal.render"Targets Reference
目标点位参考
See references/targets.md for all available targets.
所有可用目标点位请查看references/targets.md。
Target Types
目标类型
| Type | Purpose | Example |
|---|---|---|
| Tile | Smart grid button on home screen | |
| Modal | Full-screen interface | |
| Block | Inline content section | |
| Menu Item | Action menu button | |
| 类型 | 用途 | 示例 |
|---|---|---|
| 磁贴(Tile) | 首页智能网格按钮 | |
| 模态框(Modal) | 全屏界面 | |
| 区块(Block) | 行内内容区域 | |
| 菜单项(Menu Item) | 操作菜单按钮 | |
Common Target Patterns
常见目标点位模式
Home Screen (Smart Grid)
tsx
// Tile.tsx - Entry point on POS home
import { Tile, reactExtension } from '@shopify/ui-extensions-react/point-of-sale';
export default reactExtension('pos.home.tile.render', () => <TileComponent />);
function TileComponent() {
return <Tile title="My App" subtitle="Tap to open" enabled={true} />;
}Modal (Full Screen)
tsx
// Modal.tsx - Launches when tile is tapped
import { Screen, Navigator, Text, Button, useApi, reactExtension } from '@shopify/ui-extensions-react/point-of-sale';
export default reactExtension('pos.home.modal.render', () => <ModalComponent />);
function ModalComponent() {
const api = useApi<'pos.home.modal.render'>();
return (
<Navigator>
<Screen name="Main" title="My Extension">
<Text>Welcome to my POS extension</Text>
<Button title="Close" onPress={() => api.navigation.dismiss()} />
</Screen>
</Navigator>
);
}Block (Inline Content)
tsx
// ProductBlock.tsx
import { Section, Text, reactExtension, useApi } from '@shopify/ui-extensions-react/point-of-sale';
export default reactExtension('pos.product-details.block.render', () => <ProductBlock />);
function ProductBlock() {
const { product } = useApi<'pos.product-details.block.render'>();
const productData = product.getProduct();
return (
<Section title="Custom Info">
<Text>Product ID: {productData?.id}</Text>
</Section>
);
}首页(智能网格)
tsx
// Tile.tsx - Entry point on POS home
import { Tile, reactExtension } from '@shopify/ui-extensions-react/point-of-sale';
export default reactExtension('pos.home.tile.render', () => <TileComponent />);
function TileComponent() {
return <Tile title="My App" subtitle="Tap to open" enabled={true} />;
}模态框(全屏)
tsx
// Modal.tsx - Launches when tile is tapped
import { Screen, Navigator, Text, Button, useApi, reactExtension } from '@shopify/ui-extensions-react/point-of-sale';
export default reactExtension('pos.home.modal.render', () => <ModalComponent />);
function ModalComponent() {
const api = useApi<'pos.home.modal.render'>();
return (
<Navigator>
<Screen name="Main" title="My Extension">
<Text>Welcome to my POS extension</Text>
<Button title="Close" onPress={() => api.navigation.dismiss()} />
</Screen>
</Navigator>
);
}区块(行内内容)
tsx
// ProductBlock.tsx
import { Section, Text, reactExtension, useApi } from '@shopify/ui-extensions-react/point-of-sale';
export default reactExtension('pos.product-details.block.render', () => <ProductBlock />);
function ProductBlock() {
const { product } = useApi<'pos.product-details.block.render'>();
const productData = product.getProduct();
return (
<Section title="Custom Info">
<Text>Product ID: {productData?.id}</Text>
</Section>
);
}Components Reference
组件参考
See references/components.md for all available components.
所有可用组件请查看references/components.md。
Key Components
核心组件
Layout & Structure
- - Navigation screen with title, loading state, actions
Screen - - Screen navigation container
Navigator - - Scrollable content container
ScrollView - - Card-like grouping container
Section - - Horizontal/vertical layout
Stack - - Structured data rows
List
Actions
- - Tappable action button
Button - - Smart grid tile (home screen only)
Tile - - Make components tappable
Selectable
Forms
- ,
TextField- Text inputTextArea - - Numeric input
NumberField - - Email with validation
EmailField - ,
DateField- Date selectionDatePicker - - Single selection
RadioButtonList - - Increment/decrement control
Stepper - - Secure PIN entry
PinPad
Feedback
- - Important messages
Banner - - Confirmation prompts
Dialog - - Status indicators
Badge
Media
- - POS icon catalog
Icon - - Visual content
Image - - Barcode/QR scanning
CameraScanner
布局与结构
- - 带标题、加载状态、操作项的导航屏幕
Screen - - 屏幕导航容器
Navigator - - 可滚动内容容器
ScrollView - - 卡片式分组容器
Section - - 水平/垂直布局容器
Stack - - 结构化数据行组件
List
操作组件
- - 可点击操作按钮
Button - - 智能网格磁贴(仅首页可用)
Tile - - 让组件支持点击交互
Selectable
表单组件
- 、
TextField- 文本输入组件TextArea - - 数值输入组件
NumberField - - 带校验的邮箱输入组件
EmailField - 、
DateField- 日期选择组件DatePicker - - 单选组件
RadioButtonList - - 增减计数组件
Stepper - - 安全PIN码输入组件
PinPad
反馈组件
- - 重要消息提示
Banner - - 确认弹窗
Dialog - - 状态指示器
Badge
媒体组件
- - POS图标库
Icon - - 可视化内容组件
Image - - 条码/二维码扫描组件
CameraScanner
APIs Reference
API参考
See references/apis.md for all available APIs.
所有可用API请查看references/apis.md。
Accessing APIs
访问API
tsx
import { useApi } from '@shopify/ui-extensions-react/point-of-sale';
function MyComponent() {
const api = useApi<'pos.home.modal.render'>();
// Access various APIs based on target
const { cart, customer, session, navigation, toast } = api;
}tsx
import { useApi } from '@shopify/ui-extensions-react/point-of-sale';
function MyComponent() {
const api = useApi<'pos.home.modal.render'>();
// Access various APIs based on target
const { cart, customer, session, navigation, toast } = api;
}Core APIs
核心API
Cart API - Modify cart contents
tsx
const { cart } = useApi<'pos.home.modal.render'>();
// Add item
await cart.addLineItem({ variantId: 'gid://shopify/ProductVariant/123', quantity: 1 });
// Apply discount
await cart.applyCartDiscount({ type: 'percentage', value: 10, title: '10% Off' });
// Get cart
const currentCart = cart.getCart();Session API - Authentication and session data
tsx
const { session } = useApi<'pos.home.modal.render'>();
// Get session token for backend auth
const token = await session.getSessionToken();
// Get current staff member
const staff = session.currentSession;Customer API - Customer data access
tsx
const { customer } = useApi<'pos.customer-details.block.render'>();
const customerData = customer.getCustomer();Toast API - Show notifications
tsx
const { toast } = useApi<'pos.home.modal.render'>();
toast.show('Item added successfully');Navigation API - Screen navigation
tsx
const { navigation } = useApi<'pos.home.modal.render'>();
navigation.dismiss(); // Close modal
navigation.navigate('ScreenName'); // Navigate to screenScanner API - Barcode scanning
tsx
const { scanner } = useApi<'pos.home.modal.render'>();
const result = await scanner.scanBarcode();Print API - Receipt printing
tsx
const { print } = useApi<'pos.home.modal.render'>();
await print.printDocument(documentContent);购物车API - 修改购物车内容
tsx
const { cart } = useApi<'pos.home.modal.render'>();
// Add item
await cart.addLineItem({ variantId: 'gid://shopify/ProductVariant/123', quantity: 1 });
// Apply discount
await cart.applyCartDiscount({ type: 'percentage', value: 10, title: '10% Off' });
// Get cart
const currentCart = cart.getCart();会话API - 身份验证和会话数据
tsx
const { session } = useApi<'pos.home.modal.render'>();
// Get session token for backend auth
const token = await session.getSessionToken();
// Get current staff member
const staff = session.currentSession;客户API - 客户数据访问
tsx
const { customer } = useApi<'pos.customer-details.block.render'>();
const customerData = customer.getCustomer();Toast API - 展示通知
tsx
const { toast } = useApi<'pos.home.modal.render'>();
toast.show('Item added successfully');导航API - 屏幕导航
tsx
const { navigation } = useApi<'pos.home.modal.render'>();
navigation.dismiss(); // Close modal
navigation.navigate('ScreenName'); // Navigate to screen扫码API - 条码扫描
tsx
const { scanner } = useApi<'pos.home.modal.render'>();
const result = await scanner.scanBarcode();打印API - 收据打印
tsx
const { print } = useApi<'pos.home.modal.render'>();
await print.printDocument(documentContent);Direct GraphQL API Access
直接访问GraphQL API
Available for extensions targeting or later (requires POS 10.6.0+).
2025-07tsx
const response = await fetch('shopify:admin/api/graphql.json', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
query: `
query GetProduct($id: ID!) {
product(id: $id) {
title
variants(first: 10) {
nodes { id title inventoryQuantity }
}
}
}
`,
variables: { id: 'gid://shopify/Product/123' }
})
});Declare required scopes in :
shopify.app.tomltoml
[access_scopes]
scopes = "read_products,write_products,read_customers"适用于目标版本为及更高的扩展(需要POS 10.6.0+版本)。
2025-07tsx
const response = await fetch('shopify:admin/api/graphql.json', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
query: `
query GetProduct($id: ID!) {
product(id: $id) {
title
variants(first: 10) {
nodes { id title inventoryQuantity }
}
}
}
`,
variables: { id: 'gid://shopify/Product/123' }
})
});在中声明所需权限:
shopify.app.tomltoml
[access_scopes]
scopes = "read_products,write_products,read_customers"Development Workflow
开发工作流
Local Development
本地开发
bash
shopify app devOpen the Shopify POS app on your device and connect to the development store.
bash
shopify app dev在你的设备上打开Shopify POS应用并连接到开发商店。
Testing
测试
- Install app on development store
- Open Shopify POS app
- Navigate to smart grid (home) to see tiles
- Tap tiles to test modals
- Navigate to relevant screens (products, customers, orders) for block/action targets
- 在开发商店安装应用
- 打开Shopify POS应用
- 进入智能网格(首页)查看磁贴
- 点击磁贴测试模态框功能
- 进入对应页面(商品、客户、订单)测试区块/操作目标点位
Deployment
部署
bash
shopify app deploybash
shopify app deployBest Practices
最佳实践
- Performance First - Extensions run in critical merchant workflows; minimize API calls and computations
- Offline Consideration - Use Storage API for data that should persist offline
- Native Feel - Use provided components to match POS design system
- Error Handling - Always handle API failures gracefully with user feedback
- Loading States - Show loading indicators during async operations
- 性能优先 - 扩展运行在商家核心工作流中,尽量减少API调用和计算量
- 离线适配 - 使用存储API处理需要离线持久化的数据
- 原生体验 - 使用官方提供的组件,匹配POS设计系统
- 错误处理 - 始终优雅处理API失败,并给用户反馈
- 加载状态 - 异步操作期间展示加载指示器
Storage API for Offline Data
离线数据存储API
tsx
const { storage } = useApi<'pos.home.modal.render'>();
// Store data
await storage.setItem('key', JSON.stringify(data));
// Retrieve data
const stored = await storage.getItem('key');
const data = stored ? JSON.parse(stored) : null;tsx
const { storage } = useApi<'pos.home.modal.render'>();
// Store data
await storage.setItem('key', JSON.stringify(data));
// Retrieve data
const stored = await storage.getItem('key');
const data = stored ? JSON.parse(stored) : null;Complete Example: Loyalty Points Extension
完整示例:积分扩展
tsx
// Tile.tsx
import { Tile, reactExtension } from '@shopify/ui-extensions-react/point-of-sale';
export default reactExtension('pos.home.tile.render', () => (
<Tile title="Loyalty Points" subtitle="Check & redeem" enabled={true} />
));
// Modal.tsx
import {
Screen, Navigator, Text, Button, Section, Stack,
useApi, reactExtension
} from '@shopify/ui-extensions-react/point-of-sale';
import { useState, useEffect } from 'react';
export default reactExtension('pos.home.modal.render', () => <LoyaltyModal />);
function LoyaltyModal() {
const { cart, session, navigation, toast } = useApi<'pos.home.modal.render'>();
const [points, setPoints] = useState(0);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetchPoints();
}, []);
async function fetchPoints() {
const token = await session.getSessionToken();
const currentCart = cart.getCart();
const customerId = currentCart?.customer?.id;
if (!customerId) {
setLoading(false);
return;
}
const res = await fetch('https://your-backend.com/api/points', {
headers: { Authorization: `Bearer ${token}` },
body: JSON.stringify({ customerId })
});
const data = await res.json();
setPoints(data.points);
setLoading(false);
}
async function redeemPoints() {
await cart.applyCartDiscount({
type: 'fixedAmount',
value: points / 100,
title: 'Loyalty Redemption'
});
toast.show('Points redeemed!');
navigation.dismiss();
}
return (
<Navigator>
<Screen name="Main" title="Loyalty Points" isLoading={loading}>
<Section title="Current Balance">
<Stack direction="vertical" spacing={2}>
<Text variant="headingLarge">{points} points</Text>
<Text>Worth ${(points / 100).toFixed(2)}</Text>
</Stack>
</Section>
<Button
title="Redeem All Points"
type="primary"
onPress={redeemPoints}
disabled={points === 0}
/>
<Button title="Close" onPress={() => navigation.dismiss()} />
</Screen>
</Navigator>
);
}tsx
// Tile.tsx
import { Tile, reactExtension } from '@shopify/ui-extensions-react/point-of-sale';
export default reactExtension('pos.home.tile.render', () => (
<Tile title="Loyalty Points" subtitle="Check & redeem" enabled={true} />
));
// Modal.tsx
import {
Screen, Navigator, Text, Button, Section, Stack,
useApi, reactExtension
} from '@shopify/ui-extensions-react/point-of-sale';
import { useState, useEffect } from 'react';
export default reactExtension('pos.home.modal.render', () => <LoyaltyModal />);
function LoyaltyModal() {
const { cart, session, navigation, toast } = useApi<'pos.home.modal.render'>();
const [points, setPoints] = useState(0);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetchPoints();
}, []);
async function fetchPoints() {
const token = await session.getSessionToken();
const currentCart = cart.getCart();
const customerId = currentCart?.customer?.id;
if (!customerId) {
setLoading(false);
return;
}
const res = await fetch('https://your-backend.com/api/points', {
headers: { Authorization: `Bearer ${token}` },
body: JSON.stringify({ customerId })
});
const data = await res.json();
setPoints(data.points);
setLoading(false);
}
async function redeemPoints() {
await cart.applyCartDiscount({
type: 'fixedAmount',
value: points / 100,
title: 'Loyalty Redemption'
});
toast.show('Points redeemed!');
navigation.dismiss();
}
return (
<Navigator>
<Screen name="Main" title="Loyalty Points" isLoading={loading}>
<Section title="Current Balance">
<Stack direction="vertical" spacing={2}>
<Text variant="headingLarge">{points} points</Text>
<Text>Worth ${(points / 100).toFixed(2)}</Text>
</Stack>
</Section>
<Button
title="Redeem All Points"
type="primary"
onPress={redeemPoints}
disabled={points === 0}
/>
<Button title="Close" onPress={() => navigation.dismiss()} />
</Screen>
</Navigator>
);
}