Beansmile
博客

Shopify AI Toolkit 实战指南:让 AI 成为你的开发加速器

Shopify AI Toolkit 实战指南,通过真实场景展示如何快速生成 GraphQL 查询、搭建 App 架构、自动化批量操作、处理 Webhook 与开发 UI 扩展,全面提升开发效率。

前言

从查阅文档到编写代码,从调试错误到优化性能,Shopify 开发者的日常工作中充斥着大量重复性劳动。

Shopify AI Toolkit 的推出,正在改变这一切。它不是一个简单的代码生成器,而是一个深度集成 Shopify 平台知识的智能开发助手。

本文不讲空泛的概念,直接通过实战案例,展示如何用 Shopify AI Toolkit 提升开发效率。

第一部分:快速开始

安装(一行命令搞定)

# Claude Code 用户
/plugin marketplace add Shopify/shopify-ai-toolkit
/plugin install shopify-plugin@shopify-ai-toolkit

# MCP 方式(推荐)
npm install -g @shopify/dev-mcp

# 配置到 Claude Desktop
# 在 ~/Library/Application Support/Claude/claude_desktop_config.json 添加:
{
  "mcpServers": {
    "shopify-dev-mcp": {
      "command": "npx",
      "args": ["-y", "@shopify/dev-mcp@latest"]
    }
  }
}

前置要求: Node.js 18+,已安装 Claude Code / Cursor / VS Code

就这么简单,接下来直接看如何使用。

第二部分:实战场景

场景一:快速生成 GraphQL 查询

传统做法

打开 Shopify GraphQL 文档 → 查找需要的字段 → 手动编写查询 → 测试调试

AI Toolkit 做法

Prompt:

我需要获取最近 7 天内的所有订单,包括客户信息、订单金额和物流状态。请生成对应的 GraphQL 查询。

AI 输出:

query GetRecentOrders($first: Int!) {
  orders(first: $first, query: "created_at:>=7days ago") {
    edges {
      node {
        id
        orderNumber
        createdAt
        currentTotalPrice {
          amount
          currencyCode
        }
        customer {
          id
          email
          firstName
          lastName
        }
        fulfillments(first: 10) {
          edges {
            node {
              id
              status
              trackingInfo {
                company
                trackingNumber
              }
            }
          }
        }
      }
    }
    pageInfo {
      hasNextPage
      endCursor
    }
  }
}

关键优势:

  • • 字段准确,符合最新 Shopify GraphQL 规范
  • • 包含分页逻辑
  • • 自动处理时间筛选
  • • 无需翻阅文档

进阶:自动生成 TypeScript 类型

Prompt:

基于上面的查询,生成 TypeScript 接口定义

AI 输出:

interface Money {
  amount: string;
  currencyCode: string;
}

interface Customer {
  id: string;
  email: string;
  firstName: string;
  lastName: string;
}

interface TrackingInfo {
  company: string;
  trackingNumber: string;
}

interface Fulfillment {
  id: string;
  status: string;
  trackingInfo: TrackingInfo;
}

interface Order {
  id: string;
  orderNumber: number;
  createdAt: string;
  currentTotalPrice: Money;
  customer: Customer;
  fulfillments: {
    edges: Array<{
      node: Fulfillment;
    }>;
  };
}

interface OrdersResponse {
  orders: {
    edges: Array<{
      node: Order;
    }>;
    pageInfo: {
      hasNextPage: boolean;
      endCursor: string | null;
    };
  };
}

场景二:创建 Shopify App 基础架构

Prompt:

创建一个 Shopify Remix app,包含以下功能:
1. 产品批量价格更新
2. 使用 Shopify Admin API
3. 包含权限请求配置
4. 使用 Polaris 组件库

AI 会生成:

shopify.app.toml

name = "Batch Price Updater"
id = "your-app-id"

[access_scopes]
scopes = "write_products,read_products"

[app_settings]
embedded = true

[auth]
redirect_urls = [
  "https://your-app.com/auth/callback",
  "https://your-app.com/auth/shopify/callback"
]

[pos]
embedded = false

app/routes/app.products.tsx

import { json, type LoaderFunctionArgs, type ActionFunctionArgs } from "@remix-run/node";
import { useLoaderData, Form } from "@remix-run/react";
import {
  Page,
  LegacyCard,
  TextField,
  Button,
  Layout
} from "@shopify/polaris";
import { authenticate } from "../shopify.server";

export const loader = async ({ request }: LoaderFunctionArgs) => {
  const { admin, session } = await authenticate.admin(request);

  const response = await admin.graphql(
    `#graphql
    query GetProducts($first: Int!) {
      products(first: $first) {
        edges {
          node {
            id
            title
            handle
            variants(first: 1) {
              edges {
                node {
                  id
                  price
                  unitPrice {
                    amount
                    currencyCode
                  }
                }
              }
            }
          }
        }
      }
    }`,
    {
      variables: { first: 50 }
    }
  );

  const data = await response.json();
  return json(data.data.products);
};

export const action = async ({ request }: ActionFunctionArgs) => {
  const { admin } = await authenticate.admin(request);
  const formData = await request.formData();

  const updates: Array<{ id: string; price: string }> = [];

  for (const [key, value] of formData.entries()) {
    if (key.startsWith("price-")) {
      const variantId = key.replace("price-", "");
      updates.push({
        id: variantId,
        price: value.toString(),
      });
    }
  }

  const UPDATE_MUTATION = `
    mutation productVariantUpdate($input: ProductVariantInput!) {
      productVariantUpdate(input: $input) {
        productVariant {
          id
          price
        }
        userErrors {
          field
          message
        }
      }
    }
  `;

  try {
    for (const update of updates) {
      await admin.graphql(UPDATE_MUTATION, {
        variables: {
          input: {
            id: update.id,
            price: update.price,
          },
        },
      });
    }

    return json({ success: true, updatedCount: updates.length });
  } catch (error) {
    return json(
      { success: false, error: error.message },
      { status: 500 }
    );
  }
};

export default function Products() {
  const products = useLoaderData();

  return (
    <Page title="批量价格更新">
      <Layout>
        <Layout.Section>
          <LegacyCard sectioned>
            <Form method="post">
              {products?.edges?.map(({ node }: any) => (
                <LegacyCard.Section
                  key={node.id}
                  title={node.title}
                >
                  <TextField
                    label="价格"
                    name={`price-${node.variants.edges[0]?.node.id}`}
                    defaultValue={node.variants.edges[0]?.node.price}
                    type="number"
                    prefix="$"
                  />
                </LegacyCard.Section>
              ))}
              <Button submit variant="primary">
                更新所有价格
              </Button>
            </Form>
          </LegacyCard>
        </Layout.Section>
      </Layout>
    </Page>
  );
}

关键点说明:

  • • 自动处理认证authenticate.admin() 自动验证 Shopify 会话
  • • GraphQL 集成:直接使用 admin GraphQL 客户端
  • • Polaris 组件:使用 Shopify 设计系统,保证一致性
  • • 类型安全:自动推断 GraphQL 响应类型
  • • 完整的 CRUD:loader 读取数据,action 更新数据

场景三:批量操作自动化

需求: 将所有产品的描述翻译成中文

Prompt:

使用 Shopify Bulk Operations 编写一个批量操作,将所有英文产品描述翻译成中文。要求:
1. 使用 GraphQL bulkOperationRunQuery
2. 分批处理,每批 500 个产品
3. 包含错误处理和重试机制
4. 使用 Remix action 实现
5. 添加 API 速率限制处理

AI 生成代码:

import { json, type ActionFunctionArgs } from "@remix-run/node";
import { authenticate } from "../shopify.server";
import pLimit from 'p-limit';

const PRODUCT_UPDATE_MUTATION = `
  mutation productUpdate($input: ProductInput!) {
    productUpdate(input: $input) {
      product {
        id
        descriptionHtml
      }
      userErrors {
        field
        message
      }
    }
  }
`;

const BULK_QUERY = `
  mutation {
    bulkOperationRunQuery(
      query: """
        {
          products {
            edges {
              node {
                __typename
                id
                title
                descriptionHtml
              }
            }
          }
        }
      """
    ) {
      bulkOperation {
        id
        status
      }
      userErrors {
        field
        message
      }
    }
  }
`;

const limit = pLimit(2);

export const action = async ({ request }: ActionFunctionArgs) => {
  const { admin } = await authenticate.admin(request);

  try {
    const bulkResponse = await admin.graphql(BULK_QUERY);
    const bulkData = await bulkResponse.json();

    if (bulkData.data?.bulkOperationRunQuery?.userErrors?.length > 0) {
      throw new Error(bulkData.data.bulkOperationRunQuery.userErrors[0].message);
    }

    let isComplete = false;
    let attempts = 0;
    const maxAttempts = 60;

    while (!isComplete && attempts < maxAttempts) {
      await new Promise(resolve => setTimeout(resolve, 10000));

      const statusResponse = await admin.graphql(`
        {
          currentBulkOperation {
            id
            status
            errorCode
            createdAt
            completedAt
            objectCount
            fileSize
            url
          }
        }
      `);

      const statusData = await statusResponse.json();
      const status = statusData.data.currentBulkOperation;

      if (status.status === 'COMPLETED') {
        isComplete = true;

        const results = await fetchBulkResults(status.url);

        const batches = chunkArray(results, 500);
        for (const batch of batches) {
          await updateProductsBatch(admin, batch);
        }

        return json({
          success: true,
          updatedCount: results.length
        });
      } else if (status.status === 'FAILED') {
        throw new Error(`Bulk operation failed: ${status.errorCode}`);
      }

      attempts++;
    }

    if (!isComplete) {
      throw new Error('Bulk operation timed out');
    }

  } catch (error) {
    return json(
      { success: false, error: error.message },
      { status: 500 }
    );
  }
};

async function fetchBulkResults(url: string): Promise<any[]> {
  const response = await fetch(url);
  const text = await response.text();

  const products: any[] = [];
  const lines = text.split('\n').filter(line => line.trim());

  for (const line of lines) {
    try {
      const data = JSON.parse(line);
      if (data.__typename === 'Product') {
        products.push(data);
      }
    } catch (e) {
      console.error('Failed to parse line:', line);
    }
  }

  return products;
}

function chunkArray<T>(array: T[], size: number): T[][] {
  const chunks: T[][] = [];
  for (let i = 0; i < array.length; i += size) {
    chunks.push(array.slice(i, i + size));
  }
  return chunks;
}

async function updateProductsBatch(
  admin: any,
  products: any[]
): Promise<void> {
  const updatePromises = products.map((product) =>
    limit(async () => {
      const translatedDescription = await translateDescription(
        product.descriptionHtml
      );

      return admin.graphql(PRODUCT_UPDATE_MUTATION, {
        variables: {
          input: {
            id: product.id,
            descriptionHtml: translatedDescription,
          },
        },
      });
    })
  );

  await Promise.all(updatePromises);
}

async function translateDescription(text: string): Promise<string> {
  const apiKey = process.env.GOOGLE_TRANSLATE_API_KEY;

  if (!apiKey) {
    return `[中文翻译] ${text}`;
  }

  const response = await fetch(
    `https://translation.googleapis.com/language/translate/v2?key=${apiKey}`,
    {
      method: "POST",
      body: JSON.stringify({
        q: text,
        target: "zh-CN",
        source: "en",
      }),
    }
  );

  const data = await response.json();
  return data.data.translations[0].translatedText;
}

代码亮点:

  • • 完整的错误处理流程
  • • 批量操作状态轮询机制
  • • 分批处理避免超时
  • • API 速率限制处理(使用 p-limit)
  • • 集成真实的翻译 API

场景四:Webhook 处理

Prompt:

实现一个 Shopify App webhook 处理器,监听 orders/create 事件:
1. 验证 webhook 真实性
2. 解析订单数据
3. 调用第三方 API(如 ERP 系统)
4. 异步处理,立即响应
5. 使用 Remix 实现

AI 生成:

import { type ActionFunctionArgs } from "@remix-run/node";
import crypto from "crypto";

export const action = async ({ request }: ActionFunctionArgs) => {
  const rawBody = await request.text();
  const hmac = request.headers.get("X-Shopify-Hmac-Sha256");

  if (!hmac) {
    return new Response("Missing HMAC header", { status: 401 });
  }

  const computedHmac = crypto
    .createHmac("sha256", process.env.SHOPIFY_API_SECRET!)
    .update(rawBody, "utf8")
    .digest("base64");

  if (hmac !== computedHmac) {
    return new Response("Invalid HMAC", { status: 401 });
  }

  const topic = request.headers.get("X-Shopify-Topic");
  const shop = request.headers.get("X-Shopify-Shop-Domain");

  if (topic !== "orders/create") {
    return new Response("Invalid topic", { status: 400 });
  }

  try {
    const order = JSON.parse(rawBody);

    processOrderInERP(order).catch((error) => {
      console.error("ERP processing failed:", error);
      sendErrorNotification(error, order.id);
    });

    return new Response(JSON.stringify({ success: true }), {
      status: 200,
      headers: { "Content-Type": "application/json" }
    });

  } catch (error) {
    console.error("Webhook error:", error);

    return new Response(JSON.stringify({ error: "Processing failed" }), {
      status: 500,
      headers: { "Content-Type": "application/json" }
    });
  }
};

async function processOrderInERP(order: any): Promise<void> {
  const erpResponse = await fetch(process.env.ERP_API_URL!, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "Authorization": `Bearer ${process.env.ERP_API_KEY}`,
    },
    body: JSON.stringify({
      external_id: order.id,
      customer_email: order.email,
      total_price: order.total_price,
      line_items: order.line_items.map((item: any) => ({
        product_id: item.product_id,
        quantity: item.quantity,
        price: item.price,
      })),
      created_at: order.created_at,
    }),
  });

  if (!erpResponse.ok) {
    throw new Error(`ERP API error: ${erpResponse.statusText}`);
  }
}

async function sendErrorNotification(error: any, orderId: string): Promise<void> {
  console.error(`Order ${orderId} failed to process in ERP:`, error);

  if (process.env.SLACK_WEBHOOK_URL) {
    await fetch(process.env.SLACK_WEBHOOK_URL, {
      method: "POST",
      body: JSON.stringify({
        text: `Order ${orderId} failed to process in ERP`,
        error: error.message,
      }),
    });
  }
}

shopify.app.toml 配置:

[[webhooks.subscriptions]]
topic = "orders/create"
uri = "/webhooks"

[webhooks]
api_version = "2024-01"

关键点说明:

  • • HMAC 验证确保 webhook 安全性
  • • 异步处理避免超时
  • • Shopify 内置重试机制(返回非 2xx 状态码会自动重试)
  • • 添加错误通知功能

场景五:UI 扩展开发

Prompt:

创建一个 Shopify Checkout UI 扩展,在结账页面显示:
1. 自定义横幅,显示促销信息
2. 条件:订单金额超过 $100 才显示
3. 使用 React 组件
4. 遵循 Shopify 设计规范

AI 生成:

extensions/checkout-banner/src/index.jsx

import {
  extend,
  Banner,
  BlockStack,
  Text,
  useCartCost,
} from "@shopify/ui-extensions-react/checkout";

export default extend("Checkout::RenderBlock::RenderBlock", (root, api) => {
  const { cartCost } = api;
  const totalAmount = parseFloat(cartCost.totalAmount.current.amount);

  if (totalAmount < 100) {
    return;
  }

  const banner = root.createComponent(
    Banner,
    { status: "success" },
    [
      root.createComponent(
        BlockStack,
        { spacing: "base" },
        [
          root.createComponent(
            Text,
            { variant: "headingMd", as: "h3" },
            "🎉 免运费优惠"
          ),
          root.createComponent(
            Text,
            { variant: "bodyMd" },
            `订单金额 $${totalAmount.toFixed(2)} 已超过 $100,自动免运费!`
          ),
          totalAmount < 150 ? root.createComponent(
            Text,
            { variant: "bodySm", emphasis: "subtle" },
            `再购买 $${(150 - totalAmount).toFixed(2)} 即可获得额外礼品`
          ) : null,
        ]
      ),
    ]
  );

  root.appendChild(banner);
});

extensions/checkout-banner/shopify.ui.extension.toml

name = "促销横幅"
type = "checkout_ui_extension"

[[extensions.targeting]]
target = "purchase.checkout.block.render"
module = "./index.jsx"

[extensions.capabilities]
api_access = true
block_progress = true

或者使用 React 组件风格

import {
  extend,
  Banner,
  BlockStack,
  Text,
  useCartCost,
  useTranslate,
} from "@shopify/ui-extensions-react/checkout";

export default extend("Checkout::RenderBlock::RenderBlock", () => {
  const cartCost = useCartCost();
  const translate = useTranslate();

  const totalAmount = parseFloat(cartCost.totalAmount.current.amount);

  if (totalAmount < 100) {
    return null;
  }

  return (
    <Banner status="success">
      <BlockStack spacing="base">
        <Text variant="headingMd" as="h3">
          🎉 免运费优惠
        </Text>
        <Text variant="bodyMd" as="p">
          订单金额 ${totalAmount.toFixed(2)} 已超过 $100,自动免运费!
        </Text>
        {totalAmount < 150 && (
          <Text variant="bodySm" emphasis="subtle">
            再购买 ${(150 - totalAmount).toFixed(2)} 即可获得额外礼品
          </Text>
        )}
      </BlockStack>
    </Banner>
  );
});

关键点说明:

  • • 使用 @shopify/ui-extensions-react/checkout
  • • 条件渲染避免不必要的组件
  • • 支持国际化(i18n)
  • • 两种实现方式:传统 API 和 React 组件

第三部分:高级技巧

1. 多步骤开发工作流

不要一次性生成所有代码,分步骤进行更高效:

第一步:先创建数据库模型
创建一个 Prisma schema,包含产品、订单和客户的关联关系

第二步:基于模型生成 API 路由
使用 Remix 和刚才的 Prisma schema,创建 CRUD API

第三步:添加前端界面
使用 Polaris 组件创建产品管理界面

第四步:添加错误处理和测试
为所有 API 添加错误处理和单元测试

2. 迭代优化

生成初始代码后,继续优化:

优化上面的产品管理界面:
1. 添加搜索和筛选功能
2. 实现分页加载
3. 添加加载状态和错误提示
4. 优化移动端显示

3. 代码审查模式

让 AI 帮你审查代码:

请审查以下代码,检查:
1. 安全漏洞
2. 性能问题
3. Shopify 最佳实践
4. TypeScript 类型安全
[粘贴你的代码]

第四部分:常见问题处理

Q1:GraphQL 查询太复杂,如何拆分?

Prompt:

我有一个复杂的查询需要获取产品详情、库存和订单数据。帮我拆分成多个小查询,每个查询负责一个领域,避免 N+1 问题

AI 会生成:

  • • 独立的 products 查询
  • • 独立的 inventoryLevels 查询
  • • 独立的 orders 查询
  • • 包含 DataLoader 模式的批处理代码

Q2:如何处理 API 速率限制?

Prompt:

实现一个 Shopify API 请求队列,具备以下功能:
1. 自动检测速率限制(429 响应)
2. 指数退避重试
3. 请求优先级
4. 并发控制(使用 p-limit)

Q3:如何调试 GraphQL 错误?

Prompt:

创建一个 GraphQL 错误处理中间件:
1. 解析 userErrors 字段
2. 格式化错误信息
3. 提供修复建议
4. 记录错误日志

第五部分:实战项目示例

项目:库存同步工具

需求描述:

创建一个 Shopify App,实现以下功能:
1. 每 5 分钟同步一次库存到 ERP 系统
2. 使用 Shopify InventoryLevelUpdated webhook 实时更新
3. 批量处理 API 调用
4. 包含完整的错误处理和监控
5. 使用 Admin API 和 Webhooks

AI 会生成完整的项目结构:

shopify-app/
├── shopify.app.toml
├── app/
│   ├── routes/
│   │   ├── app.sync.tsx          # 同步管理界面
│   │   ├── app.webhooks.tsx       # webhook 配置
│   │   └── webhooks.ts            # webhook 处理
│   ├── services/
│   │   ├── sync.service.ts        # 同步逻辑
│   │   ├── queue.service.ts       # 任务队列
│   │   └── monitor.service.ts     # 监控服务
│   └── utils/
│       ├── api-client.ts          # API 客户端
│       └── error-handler.ts       # 错误处理
└── prisma/
    └── schema.prisma              # 数据库模型

每个文件都有完整的实现,包括:

  • • 定时任务
  • • Webhook 处理
  • • 批量操作
  • • 错误重试
  • • 监控仪表盘

结语

Shopify AI Toolkit 的真正价值,不在于"自动生成代码",而在于:

  1. 1. 减少上下文切换:无需频繁查阅文档
  2. 2. 提高代码质量:自动遵循最佳实践
  3. 3. 加速学习曲线:新手上手更快
  4. 4. 聚焦核心逻辑:让 AI 处理样板代码

关键建议:

  • • 从小任务开始,逐步扩大使用范围
  • • 始终审查 AI 生成的代码
  • • 保持对 Shopify 平台的理解
  • • 建立自己的 Prompt 模板库
  • • 注意 API 版本兼容性
  • • 处理 API 速率限制
  • • 使用异步处理避免超时

Shopify AI Toolkit 是工具,你是使用者。善用工具,但保持思考。

参考资料:

  • • Shopify AI Toolkit 官方文档
  • • Shopify GraphQL API 参考
  • • Shopify App 开发最佳实践
  • • Shopify CLI 文档
  • • Polaris 组件库

本文首发于 乐豆说