文章目录
Shopify Customer Account UI Extension 可以把自定义功能直接添加到客户账户和订单页面中。
本文演示如何构建一个客户订单备注应用。客户登录账户后,可以在订单页面点击 Add note,输入一条备注并保存。备注会被写入 Gadget 后端,并自动关联当前客户、当前订单和当前店铺。
最终功能包括:
- 在客户订单页面显示 Add note 按钮。
- 点击按钮后打开备注弹窗。
- 客户输入并保存订单备注。
- 备注与 Shopify 订单自动关联。
- 备注与当前客户和店铺自动关联。
- 客户只能读取属于自己的备注。
- 备注保存后,页面通过实时查询自动更新。
项目使用的技术栈
本项目由 Shopify 扩展前端和 Gadget 后端组成。
主要技术包括:
- Shopify Customer Account UI Extensions。
- Shopify CLI。
- Gadget 全栈应用开发平台。
- Gadget 托管的 PostgreSQL 数据库。
- Gadget 自动生成的 CRUD API。
- Gadget Shopify 连接与数据同步。
- Preact。
- JSX。
- Polaris Web Components。
- TypeScript。
- Yarn Workspaces。
- Shopify Session Token。
- Gadget Live Queries。
- Shopify Customer Account Authentication。
字幕中多次出现的 “Pact” 实际上是语音识别错误,正确名称应该是 Preact。当前 Shopify UI Extension 的新项目应以 Shopify CLI 生成的 Preact 模板和 Polaris Web Components 为准,而不是继续使用旧的 Polaris React 方案。
创建 Gadget Shopify 应用
创建新的 Gadget 项目
打开 Gadget 的应用创建流程,创建一个新的 Shopify 应用。
选择 Shopify 应用类型,并将应用设置为 Custom App。项目名称可以使用:
customer-order-notes
将项目连接到 Shopify 开发组织。开发阶段建议使用开发环境,不要一开始就创建生产环境应用配置。
这样可以把测试过程限制在 Shopify Development Store 中,避免影响真实店铺。
配置 Shopify 数据访问
这个项目需要读取客户和订单数据,因此需要在 Gadget 的 Shopify 连接设置中配置相应权限。
根据当前应用配置,启用:
- Customer 数据访问。
- Order 数据访问。
- Customer Account Authentication。
- Shopify Customer 数据模型。
- Shopify Order 数据模型。
只申请项目真正需要的权限。Shopify 默认会保护客户和订单数据,因此访问这些数据的应用需要遵守当前的受保护客户数据要求。
如果应用只服务于单个店铺,可以使用 Custom Distribution。如果未来要开发面向多个商家的 Public App,则需要准备更完整的数据使用说明、隐私政策以及 Shopify 应用审核材料。
安装到开发店铺
选择一个 Shopify Development Store,并将 Gadget 应用安装到该店铺。
安装后检查以下内容:
- Gadget 应用已经连接到开发店铺。
- Shopify API 权限已经授权。
- Customer 和 Order 数据模型已经创建。
- Webhook 已经注册。
- 最近的测试数据可以同步到 Gadget。
如果开发店铺中还没有可用订单,先创建一个测试订单,并确保这个订单属于一个可以登录客户账户的客户。
创建订单备注数据模型
创建 Note 模型
在 Gadget 中创建一个名为 Note 的数据模型。
添加一个必填字符串字段:
body
body 用于保存客户输入的订单备注。
如果应用只需要通过订单 ID 和客户 ID 查找备注,而不会对备注正文进行搜索、排序或筛选,则可以关闭这个字段的索引。
这样可以避免为不会使用的搜索场景建立索引,使数据模型更加简单。
创建客户关系
为 Note 添加一个指向 Shopify Customer 模型的关系:
Note belongs to Shopify Customer
Shopify Customer has many Notes
将这个关系设置为必填。
每一条订单备注都必须知道它属于哪个客户,这也是后续实现客户数据隔离的基础。
创建订单关系
为 Note 添加一个指向 Shopify Order 模型的关系:
Note belongs to Shopify Order
Shopify Order has many Notes
这条关系用于记录备注对应的订单。
客户在订单页面创建备注时,扩展可以从 Shopify 提供的上下文中获得当前订单 ID,并将该订单传给 Gadget 后端。
创建店铺关系
为 Note 添加一个指向 Shopify Shop 模型的关系:
Note belongs to Shopify Shop
Shopify Shop has many Notes
将这个关系设置为必填。
店铺关系对于多店铺应用尤其重要。即使多个店铺使用同一个后端,也不能让一个店铺读取另一个店铺的数据。
配置客户数据隔离
为创建操作添加跨店铺保护
创建数据模型后,Gadget 会自动生成 CRUD API,包括创建、读取、更新和删除操作。
这个项目主要使用创建和读取操作。
客户从扩展前端提交备注时,只需要传入:
- 备注正文。
- 当前订单 ID。
不应该从浏览器端直接传入客户 ID 或店铺 ID。客户和店铺关系应该由后端根据 Shopify Session Token 自动识别。
在 Gadget 的创建操作中,引入 Shopify 跨店铺数据保护方法:
import { preventCrossShopDataAccess } from "gadget-server/shopify";
然后在创建操作中应用该保护方法,使 Gadget 可以使用请求中的 Shopify Session Token,将新记录自动关联到正确的客户和店铺。
前端提交的数据可以类似于:
{
body: "Please keep this order together with the matching accessory.",
order: {
_link: orderId
}
}
前端不需要提交:
customerId
shopId
这些字段应由后端根据已验证的 Shopify 会话自动处理。
如果未来为备注增加编辑或删除功能,也应该对更新和删除操作使用相同的数据保护逻辑。
配置客户账户权限
打开 Gadget 的 Access Control 设置,找到 Shopify Storefront Customer 角色。
为 Note 模型启用:
- Create。
- Read。
读取权限必须添加客户过滤条件。过滤逻辑应当是:
Note.customerId == session.shopifyCustomerId
不同版本的 Gadget 可能会使用不同的表达式编辑器,但逻辑必须保持一致:
备注所属客户必须等于当前登录客户。
这个过滤条件不能只放在前端。前端隐藏按钮并不能真正保护数据,后端权限过滤才是客户数据隔离的核心。
将 Gadget 项目同步到本地
使用 Gadget CLI 同步项目
使用 Gadget 当前文档中提供的命令,将托管项目同步到本地开发环境。
同步后,可以使用本地编辑器开发项目,例如:
- Visual Studio Code。
- Cursor。
- Zed。
- Git。
- Shopify CLI。
本地文件和 Gadget 托管环境之间会保持同步。这样既可以使用 Gadget 的托管后端,也可以使用本地开发工具管理 Shopify UI Extension。
配置 Yarn Workspaces
Shopify 扩展会被放在 Gadget 项目的 extensions 目录中,因此需要在根目录的 package.json 中声明工作区:
{
"workspaces": [
"extensions/*"
]
}
这一步非常容易遗漏。
如果没有正确配置 Workspaces,扩展目录可能无法生成完整的 package.json,运行开发服务器时也可能无法正确解析依赖。
如果项目中有构建文件或 node_modules 不需要同步回 Gadget 托管编辑器,可以根据 Gadget 当前开发流程添加相应的忽略规则。
使用 Shopify CLI 创建扩展
生成 Customer Account UI Extension
在 Gadget 项目根目录运行:
shopify app generate extension
根据 Shopify CLI 的当前提示,选择 Customer Account UI Extension 模板。
然后将扩展连接到之前创建的 Shopify 应用,选择相同的 Shopify 组织和开发应用配置。
生成后,项目结构通常类似于:
extensions/
customer-order-notes/
shopify.extension.toml
package.json
src/
生成的前端代码使用 Preact 和 JSX。界面组件使用 Polaris Web Components,而不是旧版本的 Polaris React 组件。
不要直接复制旧教程中的 shopify.extension.toml。Shopify 的扩展 target、API 版本和 CLI 参数可能发生变化,应优先使用当前 Shopify CLI 生成的模板。
安装 Gadget 扩展依赖
按照当前 Gadget 教程,为项目安装 Shopify Extension 适配包和 Preact Hooks 包。
由于项目使用了 Yarn Workspaces,依赖应安装到项目根目录,并使用 -W 参数:
yarn add -W <Gadget Shopify Extension Package> <Gadget Preact Hooks Package>
具体包名和 import 路径应以当前 Gadget 项目生成的文档为准。将依赖安装在根目录的好处是,多个扩展可以共享同一份依赖,而不需要为每个扩展重复安装。
配置扩展入口和网络访问
添加两个扩展入口
这个项目需要两个 Customer Account UI Extension target:
- 一个用于显示订单页面上的 Add note 按钮。
- 一个用于打开备注弹窗。
在 shopify.extension.toml 中添加当前 Shopify CLI 模板支持的订单页面 target。
具体 target 名称可能会随着 Shopify API 版本和 CLI 模板变化,因此应优先复制当前生成模板中的命名方式,不要直接使用旧教程中的配置。
订单页面入口需要能够获得当前订单上下文。这个订单 ID 会在客户保存备注时传给 Gadget 后端。
谨慎启用网络访问
由于扩展需要调用 Gadget 后端,因此需要在扩展配置中启用网络访问。
网络访问不应该被默认开启。只有当扩展确实需要调用外部后端时,才应该申请和配置网络访问。
如果某些数据可以通过 Shopify Metafields 或 Shopify 提供的 API 直接读取,就不必额外通过外部网络请求获取。
在本项目中,网络访问是必要的,因为客户输入的订单备注需要写入 Gadget 数据库。
初始化 Gadget API Client
创建 API 客户端文件
在扩展的源代码目录中创建一个 API 文件,例如:
src/api.ts
根据当前 Gadget 项目生成的客户端配置,初始化 API Client:
const environment =
import.meta.env.MODE === "production"
? "production"
: "development";
export const apiClient = createGadgetClient({
environment
});
具体的客户端构造方法取决于当前安装的 Gadget 包版本。核心目标是让扩展连接到保存 Note 数据模型的 Gadget 应用。
开发环境和生产环境应当分开配置。不要在生产扩展中直接使用开发环境客户端。
注入 Shopify Session Token
Customer Account UI Extension 可以从 Shopify 扩展上下文中获取当前客户的 Session Token。
将这个 Token 传给 Gadget Provider:
function GadgetExtension() {
const { sessionToken } = shopify;
return (
<GadgetProvider
api={apiClient}
sessionToken={sessionToken}
>
<OrderNoteExtension />
</GadgetProvider>
);
}
Provider 会把 Session Token 传递给 Gadget 请求。这样,Gadget 后端就可以识别当前客户和店铺,并自动应用数据隔离规则。
构建订单备注按钮
查询当前订单是否已有备注
订单页面首先需要检查当前客户和订单是否已经存在备注。
使用 Gadget 的 Preact 查询 Hook 查询 Note 数据,并在 API Client 和 Session Token 准备完成后再发起请求。
查询条件需要包含:
- 当前 Shopify 订单 ID。
- 当前登录客户。
客户过滤应由 Gadget 后端权限规则执行,而不是只依赖前端传参。
如果当前订单已经存在备注,就隐藏 Add note 按钮。
这个教程只允许每个客户为每个订单创建一条备注。生产应用可以进一步扩展为:
- View note。
- Edit note。
- Delete note。
- Multiple notes per order。
使用 Polaris Web Components 渲染按钮
使用 Shopify Customer Account UI Extension 提供的 Polaris Web Components 渲染按钮:
<s-button
kind="secondary"
onClick={openModal}
>
Add note
</s-button>
具体属性和事件名称应与当前 Shopify CLI 生成的扩展模板保持一致。
按钮点击后,应打开与当前订单关联的备注弹窗。
构建订单备注弹窗
创建备注输入框
弹窗中需要包含:
- 备注输入框。
- 保存按钮。
- 取消按钮。
- 保存过程中的加载状态。
使用 Preact 状态保存输入内容:
const [body, setBody] = useState("");
界面可以使用 Polaris Web Components:
<s-text-area
label="Order note"
value={body}
onInput={(event) => setBody(event.currentTarget.value)}
></s-text-area>
<s-button
onClick={saveNote}
disabled={isSaving || !body.trim()}
>
{isSaving ? "Saving..." : "Save note"}
</s-button>
这里的组件属性和事件写法,应以当前 Shopify CLI 模板生成的代码为准。
调用 Gadget Create Action
使用 Gadget 的 Action Hook 调用 Note 创建操作。
客户点击 Save note 后,向后端传递:
body。- 当前 Shopify 订单 ID。
不要从扩展前端传递客户 ID 或店铺 ID。
示例逻辑如下:
const saveNote = async () => {
await createNote({
body,
order: {
_link: shopify.order.id
}
});
closeModal();
};
Gadget Provider 会自动将 Session Token 加入请求。
后端通过 Session Token 识别当前客户和店铺,再通过跨店铺数据保护逻辑自动创建关联关系。
关闭弹窗
备注成功创建后,使用 Shopify 扩展上下文关闭弹窗:
shopify.close();
取消按钮也应该关闭弹窗,但不应该发送后端请求。
保存过程中要禁用保存按钮,避免客户重复点击,导致创建多条相同备注。
使用 Live Query 更新界面
为订单页面上的备注查询启用 Gadget Live Query。
当弹窗创建备注后,Gadget 会通过实时连接将更新后的数据推送回扩展前端。
订单页面可以自动重新渲染,Add note 按钮会在备注创建后消失,不需要刷新整个页面。
这一步让 Customer Account UI Extension 的交互更接近 Shopify 原生界面。
测试 Shopify UI Extension
启动 Shopify 开发服务器
在项目根目录运行:
shopify app dev
选择与 Gadget 连接的同一个 Shopify Development Store。
Shopify CLI 会提供开发预览链接和调试工具。打开客户账户页面,进入测试订单。
使用有订单记录的客户登录
使用一个与测试订单关联的客户邮箱登录客户账户。
打开客户账户后,进入订单详情页,确认页面显示:
Add note
如果客户账户无法打开,检查邮箱验证步骤,并确认登录客户与测试订单属于同一个客户。
创建订单备注
输入一条测试备注,例如:
Please keep this order together with the matching accessory.
点击 Save note,确认以下结果:
- 保存按钮进入加载状态。
- 弹窗成功关闭。
- Add note 按钮消失。
- Gadget 的 Note 数据中出现新记录。
- 备注与正确的订单关联。
- 备注与当前客户关联。
- 备注与当前店铺关联。
验证客户数据隔离
使用另一个客户账户进行测试。
第二个客户不应该读取第一个客户创建的备注。即使两个客户在测试环境中能够看到相同订单,后端客户过滤条件仍然必须阻止第二个客户读取不属于自己的记录。
这是客户账户扩展中最重要的测试之一。不能只通过前端隐藏内容来保护客户数据,必须在后端权限层实现隔离。
发布前检查
确认 Gadget Framework 版本
字幕中的 Gadget 项目要求 Gadget Framework 1.5 或更高版本才能使用对应的 Preact 集成方式。
在发布前确认当前项目使用的框架版本,并按照 Gadget 当前迁移文档处理可能存在的 Breaking Changes。
升级后,需要重新测试:
- 客户登录。
- 订单数据读取。
- 备注创建。
- Session Token 传递。
- 客户数据隔离。
- 实时查询更新。
检查网络访问权限
检查扩展中的所有外部请求,删除不必要的网络访问配置。
如果网络访问确实必要,则确认:
- 请求域名配置正确。
- 扩展能够正常访问 Gadget 后端。
- 请求的数据范围符合应用用途。
- 隐私政策能够说明数据如何被收集和使用。
- 应用提交审核时可以解释为什么需要网络访问。
检查受保护客户数据
这个应用会处理客户创建的内容,并将备注与客户和订单关联。
发布前需要检查 Shopify 当前关于以下内容的要求:
- Customer data 访问。
- Order data 访问。
- 数据保留时间。
- 客户数据删除请求。
- 隐私政策。
- Public App 审核。
- Custom App 分发。
Custom App 和 Public App 的开发与发布流程并不完全相同。面向单个店铺的内部应用,不应直接套用面向 Shopify App Store 的公开应用流程。
部署扩展
开发测试完成后,按照当前 Shopify CLI 和 Gadget 发布流程:
- 构建 Gadget 后端。
- 确认生产环境 API Client 配置。
- 创建或选择生产 Shopify 应用配置。
- 发布包含 UI Extension 的应用版本。
- 将应用安装到目标店铺。
- 在 Checkout and Accounts Editor 中配置客户账户扩展。
- 重新测试登录、订单读取、备注创建和客户数据隔离。
这个项目可以扩展成什么
这个订单备注项目展示了多个可以复用的 Shopify 应用开发模式:
- 构建 Shopify Customer Account UI Extension。
- 使用 Shopify CLI 创建和预览扩展。
- 使用 Gadget 创建托管后端。
- 使用 Preact 构建轻量级扩展前端。
- 使用 Polaris Web Components 构建 Shopify 风格界面。
- 通过 Shopify Session Token 识别当前客户。
- 自动创建客户和店铺关系。
- 使用后端权限实现多租户数据隔离。
- 从扩展前端调用 Gadget Create Action。
- 使用 Live Query 实时更新界面。
- 通过开发店铺测试真实客户账户流程。
在这个基础上,还可以继续开发:
- 客户服务备注。
- 退货申请。
- 保修申请。
- 配送说明。
- 订阅偏好设置。
- 订单问题反馈。
- 客户与商家之间的订单沟通记录。
- 客户专属的订单附件或售后信息。
无论功能如何扩展,都应该遵循三个原则:只请求必要的数据、在后端执行权限控制、使用当前 Shopify CLI 和官方文档验证扩展配置。




