mcpField
S
shopify-admin-api

shopify-admin-api

github.com/finsilabs开发6087 次安装5 天前更新
代码仓库

关于 shopify-admin-api

面向 Shopify 应用开发者和商家内部工具开发者的技能,介绍如何通过 Admin API 读取和修改店铺数据。内容涵盖 GraphQL、REST、批量操作、库存调整及 API 调用注意事项。

主要功能

  1. 01通过 GraphQL 查询和修改数据
  2. 02使用 REST 执行简单查找
  3. 03异步批量导出大型数据集
  4. 04更新订单与库存
  5. 05处理权限、限流和错误

适用场景

  1. 01同步 Shopify 客户与 CRM
  2. 02从外部系统更新订单履约
  3. 03批量导出商品或订单
  4. 04根据仓库数据调整库存

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/finsilabs/awesome-ecommerce-skills --skill shopify-admin-api

安装后在 Claude Code 中输入/shopify-admin-api确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

tile.json
evals/bulk-product-export-with-gid-ids/
criteria.json
task.md
evals/customer-tag-management-and-rate-limit-h/
criteria.json
task.md
evals/order-fulfillment-and-usererrors-handlin/
criteria.json
task.md

详细介绍

Shopify 管理 API

概述

Shopify 管理 API 可让应用全面访问商家的店铺数据,包括产品、变体、订单、客户、库存、元字段等。它提供 GraphQL(推荐)和 REST 两种形式;GraphQL 支持精确选择字段、批量操作,并通过计算成本系统实现更好的速率限制。使用 @shopify/shopify-api Node.js 库,或通过管理 API 访问令牌直接发起 HTTP 调用。

何时使用此技能

  • 读取或写入产品目录数据(标题、变体、定价、图片、库存)时
  • 通过外部系统以编程方式履行或更新订单时
  • 在 Shopify 与 CRM 或 ERP 之间同步客户记录时
  • 使用批量操作执行大规模数据导出或导入时
  • 构建需要通过自定义应用令牌访问商家店铺的内部工具时
  • 自动化来自仓库管理系统的库存调整时

核心说明

  1. 获取管理 API 访问令牌

    对于自定义应用(单个店铺),在“管理后台 → 设置 → 应用和销售渠道 → 开发应用”中创建应用。对于公共应用或合作伙伴应用,在 OAuth 后获取令牌(参见 @shopify-app-development)。

    # .env
    SHOPIFY_ADMIN_API_TOKEN=shpat_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
    SHOPIFY_SHOP=mystore.myshopify.com
    SHOPIFY_API_VERSION=2025-01
    
  2. 初始化管理 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 });
    
  3. 使用 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;
    }
    
  4. 获取和更新订单

    // 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;
    }
    
  5. 对大型数据集运行批量操作

    导出数千个产品或订单时,请使用批量操作(仅限 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 返回 403JSONL 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