关于 shopify-admin-api
面向 Shopify 应用开发者和商家内部工具开发者的技能,介绍如何通过 Admin API 读取和修改店铺数据。内容涵盖 GraphQL、REST、批量操作、库存调整及 API 调用注意事项。
主要功能
- 01通过 GraphQL 查询和修改数据
- 02使用 REST 执行简单查找
- 03异步批量导出大型数据集
- 04更新订单与库存
- 05处理权限、限流和错误
适用场景
- 01同步 Shopify 客户与 CRM
- 02从外部系统更新订单履约
- 03批量导出商品或订单
- 04根据仓库数据调整库存
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ finsilabs/ awesome-ecommerce-skills --skill shopify-admin-api安装后在 Claude Code 中输入/shopify-admin-api确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
tile.jsonevals/bulk-product-export-with-gid-ids/criteria.jsontask.mdevals/customer-tag-management-and-rate-limit-h/criteria.jsontask.mdevals/order-fulfillment-and-usererrors-handlin/criteria.jsontask.md详细介绍
Shopify 管理 API
概述
Shopify 管理 API 可让应用全面访问商家的店铺数据,包括产品、变体、订单、客户、库存、元字段等。它提供 GraphQL(推荐)和 REST 两种形式;GraphQL 支持精确选择字段、批量操作,并通过计算成本系统实现更好的速率限制。使用 @shopify/shopify-api Node.js 库,或通过管理 API 访问令牌直接发起 HTTP 调用。
何时使用此技能
- 读取或写入产品目录数据(标题、变体、定价、图片、库存)时
- 通过外部系统以编程方式履行或更新订单时
- 在 Shopify 与 CRM 或 ERP 之间同步客户记录时
- 使用批量操作执行大规模数据导出或导入时
- 构建需要通过自定义应用令牌访问商家店铺的内部工具时
- 自动化来自仓库管理系统的库存调整时
核心说明
-
获取管理 API 访问令牌
对于自定义应用(单个店铺),在“管理后台 → 设置 → 应用和销售渠道 → 开发应用”中创建应用。对于公共应用或合作伙伴应用,在 OAuth 后获取令牌(参见
@shopify-app-development)。# .env SHOPIFY_ADMIN_API_TOKEN=shpat_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx SHOPIFY_SHOP=mystore.myshopify.com SHOPIFY_API_VERSION=2025-01 -
初始化管理 API 客户端
// lib/shopify-admin.ts import { shopifyApi, ApiVersion, Session } from "@shopify/shopify-api"; import "@shopify/shopify-api/adapters/node"; const shopify = shopifyApi({ apiKey: process.env.SHOPIFY_API_KEY!, apiSecretKey: process.env.SHOPIFY_API_SECRET!, scopes: ["read_products", "write_products", "read_orders", "write_orders"], hostName: process.env.SHOPIFY_APP_URL!, apiVersion: ApiVersion.January25, isEmbeddedApp: false, // true for merchant-facing embedded apps }); // For custom apps with a static token const session = new Session({ id: `offline_${process.env.SHOPIFY_SHOP}`, shop: process.env.SHOPIFY_SHOP!, state: "", isOnline: false, accessToken: process.env.SHOPIFY_ADMIN_API_TOKEN, }); export const adminClient = new shopify.clients.Graphql({ session }); export const restClient = new shopify.clients.Rest({ session }); -
使用 GraphQL 查询产品
// Fetch products with variants and inventory export async function getProducts(cursor?: string) { const response = await adminClient.request(` query GetProducts($cursor: String) { products(first: 50, after: $cursor) { pageInfo { hasNextPage endCursor } edges { node { id title status variants(first: 100) { edges { node { id sku price inventoryQuantity inventoryItem { id } } } } } } } } `, { variables: { cursor } }); return response.data.products; } // Update a product's price via mutation export async function updateVariantPrice(variantId: string, price: string) { const response = await adminClient.request(` mutation UpdateVariantPrice($id: ID!, $price: Money!) { productVariantUpdate(input: { id: $id, price: $price }) { productVariant { id price } userErrors { field message } } } `, { variables: { id: variantId, price } }); const { userErrors } = response.data.productVariantUpdate; if (userErrors.length > 0) throw new Error(userErrors[0].message); return response.data.productVariantUpdate.productVariant; } -
获取和更新订单
// Query unfulfilled orders export async function getUnfulfilledOrders() { const response = await adminClient.request(` query { orders(first: 50, query: "fulfillment_status:unfulfilled financial_status:paid") { edges { node { id name email createdAt lineItems(first: 50) { edges { node { title quantity variant { id sku } } } } shippingAddress { firstName lastName address1 city province zip country } } } } } `); return response.data.orders.edges.map(({ node }: any) => node); } // Mark an order as fulfilled export async function fulfillOrder(orderId: string, trackingNumber: string, trackingCompany: string) { // First get fulfillment order ID const orderResponse = await adminClient.request(` query GetFulfillmentOrders($id: ID!) { order(id: $id) { fulfillmentOrders(first: 5) { edges { node { id status lineItems(first: 20) { edges { node { id remainingQuantity } } } } } } } } `, { variables: { id: orderId } }); const fulfillmentOrder = orderResponse.data.order.fulfillmentOrders.edges[0]?.node; if (!fulfillmentOrder) throw new Error("No fulfillment order found"); const response = await adminClient.request(` mutation FulfillOrder($fulfillment: FulfillmentInput!) { fulfillmentCreate(fulfillment: $fulfillment) { fulfillment { id status } userErrors { field message } } } `, { variables: { fulfillment: { lineItemsByFulfillmentOrder: [{ fulfillmentOrderId: fulfillmentOrder.id }], trackingInfo: { number: trackingNumber, company: trackingCompany }, notifyCustomer: true, }, }, }); return response.data.fulfillmentCreate; } -
对大型数据集运行批量操作
导出数千个产品或订单时,请使用批量操作(仅限 GraphQL)——操作会异步运行,并返回 JSONL 文件 URL:
// Start a bulk operation export async function startBulkProductExport() { const response = await adminClient.request(` mutation { bulkOperationRunQuery( query: """ { products { edges { node { id title status variants { edges { node { id sku price inventoryQuantity } } } } } } } """ ) { bulkOperation { id status } userErrors { field message } } } `); return response.data.bulkOperationRunQuery.bulkOperation; } // Poll for completion and download URL export async function getBulkOperationStatus() { const response = await adminClient.request(` query { currentBulkOperation { id status errorCode objectCount url # JSONL download URL — available when status is COMPLETED } } `); return response.data.currentBulkOperation; }
示例
通过 REST API 搜索和更新客户
// REST is still valid for simple lookups where GraphQL overhead isn't worth it
export async function searchCustomers(email: string) {
const response = await restClient.get({
path: "customers/search",
query: { query: `email:${email}` },
});
return response.body.customers;
}
export async function tagCustomer(customerId: number, tags: string[]) {
const response = await restClient.put({
path: `customers/${customerId}`,
data: { customer: { id: customerId, tags: tags.join(",") } },
});
return response.body.customer;
}
库存调整
export async function adjustInventory(inventoryItemId: string, locationId: string, delta: number) {
const response = await adminClient.request(`
mutation AdjustInventory($input: InventoryAdjustQuantitiesInput!) {
inventoryAdjustQuantities(input: $input) {
inventoryAdjustmentGroup {
changes {
name
delta
item { id }
location { name }
}
}
userErrors { field message }
}
}
`, {
variables: {
input: {
name: "available",
reason: "correction",
changes: [
{
inventoryItemId,
locationId,
delta,
},
],
},
},
});
return response.data.inventoryAdjustQuantities;
}
最佳实践
- 优先使用 GraphQL,而非 REST——GraphQL 基于成本的速率限制为每秒 1000 个成本单位,比 REST 每秒 40 次请求更宽松;它还能避免获取过多数据
- 导出超过 250 条记录时使用批量操作——切勿手动翻页处理数千条记录;批量操作可在单个异步作业中处理多达数百万条记录
- 始终处理变更中的
userErrors——HTTP 响应为 200 并不代表操作成功;应检查userErrors数组,再判定变更是否成功 - 使用
gid://shopify/Product/123格式的 ID——管理 API GraphQL 使用全局 ID;切勿发送不带 GID 前缀的数字 ID - 实现指数退避——遵循
Retry-After标头,并针对 429 和 503 响应实现退避 - 缓存不可变的产品数据——产品标题和句柄很少更改;使用合理的 TTL 缓存这些数据,以减少 API 调用
- 仅请求最低限度的必要权限——请求较少的权限范围可减少安装时商家的信任阻力
常见问题
| 问题 | 解决方案 |
|---|---|
GraphQL 错误 Cost exceeds bucket size | 减小连接上的 first: 参数(使用 50,而不是 250),或调整查询结构,避免连接嵌套过深 |
| 数字 ID 与 GID 格式不匹配 | 始终将 REST 数字 ID 转换为 GID 格式:gid://shopify/Product/${numericId} |
| 批量操作 URL 返回 403 | JSONL URL 是有时限的签名 S3 URL——轮询到 COMPLETED 状态后立即下载 |
订单履行失败并提示 FULFILLMENT_ORDER_NOT_FOUND | 自 API 版本 2022-07 起,必须通过履行订单 API(而非旧版履行 API)履行订单 |
| Webhook 事件触发重复处理 | 使用 Webhook 负载中的 admin_graphql_api_id,并实现幂等键机制 |
| 更新客户时清除了现有标签 | 更新标签时,务必先获取当前标签并在此基础上追加——API 会替换标签,而非合并 |
相关技能
- @shopify-app-development
- @shopify-webhooks
- @shopify-metafields
- @shopify-storefront-api
- @bulk-data-operations
评论
支持 Markdown