关于 shopify-app-development
面向 Shopify 应用开发者的技能,指导使用 Shopify CLI 3.x 的 Remix 模板构建应用,涵盖嵌入式应用、OAuth、Admin API 和 Polaris 界面。
主要功能
- 01Remix 应用脚手架
- 02OAuth 与会话处理
- 03Admin API 访问
- 04Polaris 界面构建指南
- 05Webhook 与部署指南
适用场景
- 01启动公共或自定义 Shopify 应用
- 02构建商家使用的嵌入式应用
- 03添加经身份验证的 Admin API 操作
- 04将 Express 应用迁移到现代架构
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ finsilabs/ awesome-ecommerce-skills --skill shopify-app-development安装后在 Claude Code 中输入/shopify-app-development确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
tile.jsonevals/embedded-ui-with-polaris-and-app-bridge/criteria.jsontask.mdevals/gdpr-webhooks-and-hmac-validation/criteria.jsontask.mdevals/production-session-storage-and-api-versi/criteria.jsontask.md详细介绍
Shopify 应用开发
概述
使用 Shopify CLI 3.x Remix 模板构建 Shopify 应用。该模板会自动处理 OAuth 令牌交换、会话存储和 App Bridge 初始化。嵌入式应用运行在 Shopify Admin iframe 中,并使用 Polaris 打造原生般的界面。现代方案使用基于 Remix 的 @shopify/shopify-app-remix 包,而不是旧版 Express 模板。
何时使用此技能
- 构建扩展 Admin 功能的公共或自定义 Shopify 应用时
- 创建商家从 Shopify App Store 安装的嵌入式应用时
- 首次实现 OAuth,并在重新安装后持久保留会话时
- 需要代表已认证的商家访问 Admin API 时
- 使用 Shopify 的 Polaris 设计系统构建面向商家的工具时
- 将较旧的 Express/koa Shopify 应用迁移到现代 Remix 技术栈时
核心说明
-
使用 Shopify CLI 搭建应用
npm install -g @shopify/cli @shopify/theme shopify app init my-shopify-app # Choose: Remix template cd my-shopify-app shopify app dev这会搭建一个 Remix 应用,并预先配置 OAuth、会话存储(默认使用 SQLite)和 App Bridge。开发命令会通过 Cloudflare 为本地服务建立隧道,并将应用安装到 Partner 开发商店中。
-
了解 OAuth 流程和会话处理
搭建的应用使用
@shopify/shopify-app-remix处理 OAuth 流程。在app/shopify.server.ts中:import "@shopify/shopify-app-remix/adapters/node"; import { AppDistribution, DeliveryMethod, shopifyApp, LATEST_API_VERSION, } from "@shopify/shopify-app-remix/server"; import { PrismaSessionStorage } from "@shopify/shopify-app-session-storage-prisma"; import { PrismaClient } from "@prisma/client"; const prisma = new PrismaClient(); const shopify = shopifyApp({ apiKey: process.env.SHOPIFY_API_KEY, apiSecretKey: process.env.SHOPIFY_API_SECRET || "", apiVersion: LATEST_API_VERSION, scopes: process.env.SCOPES?.split(","), appUrl: process.env.SHOPIFY_APP_URL || "", authPathPrefix: "/auth", sessionStorage: new PrismaSessionStorage(prisma), distribution: AppDistribution.AppStore, webhooks: { APP_UNINSTALLED: { deliveryMethod: DeliveryMethod.Http, callbackUrl: "/webhooks", }, }, hooks: { afterAuth: async ({ session }) => { shopify.registerWebhooks({ session }); }, }, }); export default shopify; export const authenticate = shopify.authenticate; -
保护路由并调用 Admin API
任何需要访问 Admin API 的 loader 或 action 都会调用
authenticate.admin:// app/routes/app._index.tsx import { json } from "@remix-run/node"; import { useLoaderData } from "@remix-run/react"; import { authenticate } from "../shopify.server"; export const loader = async ({ request }: LoaderFunctionArgs) => { const { admin, session } = await authenticate.admin(request); // GraphQL Admin API call const response = await admin.graphql(` query { shop { name email primaryDomain { url } } } `); const { data } = await response.json(); return json({ shop: data.shop }); }; export default function Index() { const { shop } = useLoaderData<typeof loader>(); return <Page title={`Hello, ${shop.name}`} />; } -
使用 Polaris 组件构建 UI
// app/routes/app.products.tsx import { Page, Layout, Card, DataTable, Button, Banner, } from "@shopify/polaris"; import { TitleBar, useAppBridge } from "@shopify/app-bridge-react"; export default function ProductsPage() { const shopify = useAppBridge(); const handleSave = async () => { // Use App Bridge Toast for notifications inside the iframe shopify.toast.show("Products updated successfully"); }; return ( <Page> <TitleBar title="Products" primaryAction={{ content: "Save", onAction: handleSave }} /> <Layout> <Layout.Section> <Card> <DataTable columnContentTypes={["text", "numeric", "numeric"]} headings={["Product", "Price", "Inventory"]} rows={[["Widget A", "$19.99", 42]]} /> </Card> </Layout.Section> </Layout> </Page> ); } -
配置作用域并处理应用重新安装
在
shopify.app.toml中定义所需作用域:name = "my-shopify-app" client_id = "your_api_key" application_url = "https://your-app.fly.dev" embedded = true [access_scopes] scopes = "read_products,write_products,read_orders" [webhooks] api_version = "2025-01" [[webhooks.subscriptions]] topics = ["app/uninstalled"] uri = "/webhooks"处理 GDPR 强制要求的 webhook(
customers/data_request、customers/redact、shop/redact),即使应用不存储个人数据也不例外——Shopify 要求提供这些端点。 -
部署到生产环境
shopify app deploy # Deploys to Shopify (functions/extensions) # Deploy the Remix server separately (Fly.io, Railway, Render) fly launch fly deploy
示例
通过 Admin GraphQL API 执行变更
// Create a product via the Admin API inside a Remix action
export const action = async ({ request }: ActionFunctionArgs) => {
const { admin } = await authenticate.admin(request);
const response = await admin.graphql(
`#graphql
mutation CreateProduct($input: ProductInput!) {
productCreate(input: $input) {
product {
id
title
handle
}
userErrors {
field
message
}
}
}`,
{
variables: {
input: {
title: "New Product",
vendor: "My Store",
productType: "Widget",
tags: ["new", "featured"],
},
},
}
);
const { data } = await response.json();
if (data.productCreate.userErrors.length > 0) {
return json({ errors: data.productCreate.userErrors }, { status: 422 });
}
return json({ product: data.productCreate.product });
};
App Bridge 资源选择器(v4)
import { useAppBridge } from "@shopify/app-bridge-react";
import { useState } from "react";
import { Button } from "@shopify/polaris";
export default function ProductSelector() {
const shopify = useAppBridge();
const [selected, setSelected] = useState<string[]>([]);
const handleSelectProducts = async () => {
const selection = await shopify.resourcePicker({
type: "product",
multiple: true,
});
if (selection) {
setSelected(selection.map((p) => p.id));
}
};
return (
<>
<Button onClick={handleSelectProducts}>Select Products</Button>
<p>Selected IDs: {selected.join(", ")}</p>
</>
);
}
最佳实践
- 使用 Remix CLI 模板 — 它会自动处理会话存储、CSRF、OAuth 令牌刷新和 frame-ancestor CSP 标头
- 将会话存储在持久化数据库中(生产环境使用 Prisma + PostgreSQL)— 默认的 SQLite 存储不适用于多实例部署
- 过度申请作用域会降低转化率 — 仅申请所需的最小作用域;商家在安装期间会看到作用域列表
- 使用 App Bridge 进行导航和显示模态框 — 直接使用
window.location导航会破坏嵌入式 iframe 上下文 - 验证 webhook HMAC 签名 — 即使是不会采取任何操作的 GDPR 强制要求 webhook,也要验证
- 测试重新安装流程 — 商家卸载并重新安装后,必须获得新的 OAuth 令牌,且不能残留旧会话数据
- 仅在开发环境中使用
LATEST_API_VERSION— 在生产环境中固定到特定版本(例如2025-01),以避免发生破坏性变更 - 处理
payment_required错误 — 如果商家超出其套餐限制,App Store 中的应用可能会遇到账单要求错误
常见问题
| 问题 | 解决方案 |
|---|---|
| “Refused to display in frame” CSP 错误 | 确保 Remix 服务在 Content-Security-Policy 中返回 frame-ancestors https://*.myshopify.com https://admin.shopify.com |
| 安装后 OAuth 重定向循环 | 检查 shopify.app.toml 中的应用 URL 是否与服务可访问的 URL 一致——不匹配会导致无限重定向 |
| 后续请求中找不到会话 | 使用持久化会话存储(Prisma/PostgreSQL);SQLite 无法在 Fly.io 或 Render 实例之间使用 |
App Bridge 的 useAppBridge() 返回 null | 使用 @shopify/shopify-app-remix/react 中的 <AppProvider> 包裹 Remix 应用根组件 |
| 已注册 Webhook,但未触发 | 在 afterAuth 期间注册的 Webhook 可能不会在应用更新后保留——在单独的路由中调用 shopify.registerWebhooks 进行验证 |
| Webhook 端点出现“Invalid HMAC” | 确保在任何 JSON 解析中间件运行前读取原始请求正文——在 Express/Remix 解析请求正文前使用 getRawBody |
相关技能
- @shopify-admin-api
- @shopify-webhooks
- @shopify-checkout-extensions
- @shopify-storefront-api
- @oauth-implementation