shopify-pos

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Shopify 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:
  1. Targets - Where your extension appears (tile, modal, block, menu item)
  2. Target APIs - Data and functionality access (Cart, Customer, Session, etc.)
  3. Components - Native UI building blocks (Button, Screen, List, etc.)
POS UI扩展包含三个相互关联的部分:
  1. 目标点位(Targets) - 扩展的展示位置(磁贴、模态框、区块、菜单项)
  2. 目标点位API - 数据和功能访问权限(购物车、客户、会话等)
  3. 组件 - 原生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

目标类型

TypePurposeExample
TileSmart grid button on home screen
pos.home.tile.render
ModalFull-screen interface
pos.home.modal.render
BlockInline content section
pos.product-details.block.render
Menu ItemAction menu button
pos.customer-details.action.menu-item.render
类型用途示例
磁贴(Tile)首页智能网格按钮
pos.home.tile.render
模态框(Modal)全屏界面
pos.home.modal.render
区块(Block)行内内容区域
pos.product-details.block.render
菜单项(Menu Item)操作菜单按钮
pos.customer-details.action.menu-item.render

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
  • Screen
    - Navigation screen with title, loading state, actions
  • Navigator
    - Screen navigation container
  • ScrollView
    - Scrollable content container
  • Section
    - Card-like grouping container
  • Stack
    - Horizontal/vertical layout
  • List
    - Structured data rows
Actions
  • Button
    - Tappable action button
  • Tile
    - Smart grid tile (home screen only)
  • Selectable
    - Make components tappable
Forms
  • TextField
    ,
    TextArea
    - Text input
  • NumberField
    - Numeric input
  • EmailField
    - Email with validation
  • DateField
    ,
    DatePicker
    - Date selection
  • RadioButtonList
    - Single selection
  • Stepper
    - Increment/decrement control
  • PinPad
    - Secure PIN entry
Feedback
  • Banner
    - Important messages
  • Dialog
    - Confirmation prompts
  • Badge
    - Status indicators
Media
  • Icon
    - POS icon catalog
  • Image
    - Visual content
  • CameraScanner
    - Barcode/QR scanning
布局与结构
  • Screen
    - 带标题、加载状态、操作项的导航屏幕
  • Navigator
    - 屏幕导航容器
  • ScrollView
    - 可滚动内容容器
  • Section
    - 卡片式分组容器
  • Stack
    - 水平/垂直布局容器
  • List
    - 结构化数据行组件
操作组件
  • Button
    - 可点击操作按钮
  • Tile
    - 智能网格磁贴(仅首页可用)
  • Selectable
    - 让组件支持点击交互
表单组件
  • TextField
    TextArea
    - 文本输入组件
  • NumberField
    - 数值输入组件
  • EmailField
    - 带校验的邮箱输入组件
  • DateField
    DatePicker
    - 日期选择组件
  • RadioButtonList
    - 单选组件
  • Stepper
    - 增减计数组件
  • PinPad
    - 安全PIN码输入组件
反馈组件
  • Banner
    - 重要消息提示
  • Dialog
    - 确认弹窗
  • Badge
    - 状态指示器
媒体组件
  • Icon
    - POS图标库
  • 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 screen
Scanner 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
2025-07
or later (requires POS 10.6.0+).
tsx
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.toml
:
toml
[access_scopes]
scopes = "read_products,write_products,read_customers"
适用于目标版本为
2025-07
及更高的扩展(需要POS 10.6.0+版本)。
tsx
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.toml
中声明所需权限:
toml
[access_scopes]
scopes = "read_products,write_products,read_customers"

Development Workflow

开发工作流

Local Development

本地开发

bash
shopify app dev
Open the Shopify POS app on your device and connect to the development store.
bash
shopify app dev
在你的设备上打开Shopify POS应用并连接到开发商店。

Testing

测试

  1. Install app on development store
  2. Open Shopify POS app
  3. Navigate to smart grid (home) to see tiles
  4. Tap tiles to test modals
  5. Navigate to relevant screens (products, customers, orders) for block/action targets
  1. 在开发商店安装应用
  2. 打开Shopify POS应用
  3. 进入智能网格(首页)查看磁贴
  4. 点击磁贴测试模态框功能
  5. 进入对应页面(商品、客户、订单)测试区块/操作目标点位

Deployment

部署

bash
shopify app deploy
bash
shopify app deploy

Best Practices

最佳实践

  1. Performance First - Extensions run in critical merchant workflows; minimize API calls and computations
  2. Offline Consideration - Use Storage API for data that should persist offline
  3. Native Feel - Use provided components to match POS design system
  4. Error Handling - Always handle API failures gracefully with user feedback
  5. Loading States - Show loading indicators during async operations
  1. 性能优先 - 扩展运行在商家核心工作流中,尽量减少API调用和计算量
  2. 离线适配 - 使用存储API处理需要离线持久化的数据
  3. 原生体验 - 使用官方提供的组件,匹配POS设计系统
  4. 错误处理 - 始终优雅处理API失败,并给用户反馈
  5. 加载状态 - 异步操作期间展示加载指示器

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>
  );
}