app-shopify-admin-graphql
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseShopify Admin GraphQL
Shopify Admin GraphQL
Use this skill when adding or changing code that talks to the Shopify Admin API via GraphQL.
当你需要添加或修改通过GraphQL调用Shopify Admin API的代码时,可以使用本技能。
When to Use
适用场景
- Querying Shopify data (shop, customers, orders, products, inventory)
- Mutating Shopify data (creating/updating customers, orders, products)
- Implementing pagination for large datasets
- Handling API throttling and rate limits
- Working with metafields or other Shopify resources
- 查询Shopify数据(店铺、客户、订单、商品、库存)
- 修改Shopify数据(创建/更新客户、订单、商品)
- 为大型数据集实现分页
- 处理API限流与速率限制
- 处理元字段或其他Shopify资源
Getting the GraphQL Client
获取GraphQL客户端
In Remix Loaders/Actions (Recommended)
在Remix Loaders/Actions中使用(推荐)
Use from :
authenticate.admin()@shopify/shopify-app-remixtypescript
import { authenticate } from "../shopify.server";
export const loader = async ({ request }: LoaderFunctionArgs) => {
const { admin } = await authenticate.admin(request);
const response = await admin.graphql(`
query GetShopDetails {
shop {
id
name
myshopifyDomain
plan {
displayName
}
}
}
`);
const { data } = await response.json();
return json({ shop: data.shop });
};使用中的:
@shopify/shopify-app-remixauthenticate.admin()typescript
import { authenticate } from "../shopify.server";
export const loader = async ({ request }: LoaderFunctionArgs) => {
const { admin } = await authenticate.admin(request);
const response = await admin.graphql(`
query GetShopDetails {
shop {
id
name
myshopifyDomain
plan {
displayName
}
}
}
`);
const { data } = await response.json();
return json({ shop: data.shop });
};With Variables
带变量的使用方式
typescript
export const action = async ({ request }: ActionFunctionArgs) => {
const { admin } = await authenticate.admin(request);
const formData = await request.formData();
const email = formData.get("email") as string;
// Validate email format to prevent query manipulation
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!email || !emailRegex.test(email)) {
return json({ error: "Invalid email format" }, { status: 400 });
}
// Escape quotes and wrap in quotes to treat as literal value
const sanitizedEmail = email.replace(/"/g, '\\"');
const response = await admin.graphql(`
query FindCustomerByEmail($query: String!) {
customers(first: 1, query: $query) {
edges {
node {
id
email
phone
firstName
lastName
}
}
}
}
`, {
variables: { query: `email:"${sanitizedEmail}"` }
});
const { data } = await response.json();
return json({ customer: data.customers.edges[0]?.node });
};typescript
export const action = async ({ request }: ActionFunctionArgs) => {
const { admin } = await authenticate.admin(request);
const formData = await request.formData();
const email = formData.get("email") as string;
// 验证邮箱格式以防止查询篡改
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!email || !emailRegex.test(email)) {
return json({ error: "Invalid email format" }, { status: 400 });
}
// 转义引号并包裹为字面量值
const sanitizedEmail = email.replace(/"/g, '\\"');
const response = await admin.graphql(`
query FindCustomerByEmail($query: String!) {
customers(first: 1, query: $query) {
edges {
node {
id
email
phone
firstName
lastName
}
}
}
}
`, {
variables: { query: `email:"${sanitizedEmail}"` }
});
const { data } = await response.json();
return json({ customer: data.customers.edges[0]?.node });
};Background Jobs / Webhooks (Offline Access)
后台任务/ Webhook(离线访问)
When you don't have a request context, use offline session tokens:
typescript
import { unauthenticated } from "../shopify.server";
export async function processWebhook(shop: string) {
const { admin } = await unauthenticated.admin(shop);
const response = await admin.graphql(`
query GetShop {
shop {
name
}
}
`);
const { data } = await response.json();
return data.shop;
}当没有请求上下文时,使用离线会话令牌:
typescript
import { unauthenticated } from "../shopify.server";
export async function processWebhook(shop: string) {
const { admin } = await unauthenticated.admin(shop);
const response = await admin.graphql(`
query GetShop {
shop {
name
}
}
`);
const { data } = await response.json();
return data.shop;
}Common Query Patterns
常见查询模式
Shop Details
店铺详情
graphql
query GetShopDetails {
shop {
id
name
email
myshopifyDomain
primaryDomain {
url
}
plan {
displayName
}
currencyCode
timezoneAbbreviation
}
}graphql
query GetShopDetails {
shop {
id
name
email
myshopifyDomain
primaryDomain {
url
}
plan {
displayName
}
currencyCode
timezoneAbbreviation
}
}Products with Pagination
带分页的商品查询
graphql
query GetProducts($first: Int!, $after: String) {
products(first: $first, after: $after) {
pageInfo {
hasNextPage
endCursor
}
edges {
node {
id
title
handle
status
variants(first: 10) {
edges {
node {
id
title
price
sku
}
}
}
}
}
}
}graphql
query GetProducts($first: Int!, $after: String) {
products(first: $first, after: $after) {
pageInfo {
hasNextPage
endCursor
}
edges {
node {
id
title
handle
status
variants(first: 10) {
edges {
node {
id
title
price
sku
}
}
}
}
}
}
}Customer Lookup
客户查询
graphql
query FindCustomer($query: String!) {
customers(first: 1, query: $query) {
edges {
node {
id
email
phone
firstName
lastName
ordersCount
totalSpent
}
}
}
}graphql
query FindCustomer($query: String!) {
customers(first: 1, query: $query) {
edges {
node {
id
email
phone
firstName
lastName
ordersCount
totalSpent
}
}
}
}Order by ID
按ID查询订单
graphql
query GetOrder($id: ID!) {
order(id: $id) {
id
name
email
phone
totalPriceSet {
shopMoney {
amount
currencyCode
}
}
lineItems(first: 50) {
edges {
node {
title
quantity
variant {
id
sku
}
}
}
}
shippingAddress {
address1
city
country
}
}
}graphql
query GetOrder($id: ID!) {
order(id: $id) {
id
name
email
phone
totalPriceSet {
shopMoney {
amount
currencyCode
}
}
lineItems(first: 50) {
edges {
node {
title
quantity
variant {
id
sku
}
}
}
}
shippingAddress {
address1
city
country
}
}
}Handling Throttling
限流处理
Shopify uses a leaky bucket algorithm for rate limiting. For bulk operations or background jobs, implement retry logic:
typescript
interface RetryOptions {
maxRetries?: number;
initialDelay?: number;
maxDelay?: number;
}
async function executeWithRetry<T>(
admin: AdminApiContext,
query: string,
variables?: Record<string, unknown>,
options: RetryOptions = {}
): Promise<T> {
const { maxRetries = 3, initialDelay = 1000, maxDelay = 10000 } = options;
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
const response = await admin.graphql(query, { variables });
const result = await response.json();
if (result.errors?.some((e: any) => e.extensions?.code === "THROTTLED")) {
throw new Error("THROTTLED");
}
return result.data as T;
} catch (error) {
if (attempt === maxRetries) throw error;
const delay = Math.min(initialDelay * Math.pow(2, attempt), maxDelay);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
throw new Error("Max retries exceeded");
}Shopify使用漏桶算法进行速率限制。对于批量操作或后台任务,请实现重试逻辑:
typescript
interface RetryOptions {
maxRetries?: number;
initialDelay?: number;
maxDelay?: number;
}
async function executeWithRetry<T>(
admin: AdminApiContext,
query: string,
variables?: Record<string, unknown>,
options: RetryOptions = {}
): Promise<T> {
const { maxRetries = 3, initialDelay = 1000, maxDelay = 10000 } = options;
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
const response = await admin.graphql(query, { variables });
const result = await response.json();
if (result.errors?.some((e: any) => e.extensions?.code === "THROTTLED")) {
throw new Error("THROTTLED");
}
return result.data as T;
} catch (error) {
if (attempt === maxRetries) throw error;
const delay = Math.min(initialDelay * Math.pow(2, attempt), maxDelay);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
throw new Error("Max retries exceeded");
}Error Handling
错误处理
typescript
const response = await admin.graphql(query, { variables });
const { data, errors } = await response.json();
if (errors) {
// Handle GraphQL errors
console.error("GraphQL errors:", errors);
// Check for specific error types
const throttled = errors.some((e: any) =>
e.extensions?.code === "THROTTLED"
);
const notFound = errors.some((e: any) =>
e.message?.includes("not found")
);
if (throttled) {
// Retry with backoff
}
}typescript
const response = await admin.graphql(query, { variables });
const { data, errors } = await response.json();
if (errors) {
// 处理GraphQL错误
console.error("GraphQL errors:", errors);
// 检查特定错误类型
const throttled = errors.some((e: any) =>
e.extensions?.code === "THROTTLED"
);
const notFound = errors.some((e: any) =>
e.message?.includes("not found")
);
if (throttled) {
// 带退避策略重试
}
}Best Practices
最佳实践
- Use operation names for debugging:
query GetShopDetails { ... } - Request only needed fields to reduce response size and improve performance
- Use pagination for lists - never request unbounded data
- Handle errors gracefully - check for both array and HTTP errors
errors - Implement retries with exponential backoff for background jobs
- Use fragments for repeated field selections across queries
- Prefer GraphQL over REST for complex queries with relationships
- 使用操作名称便于调试:
query GetShopDetails { ... } - 仅请求所需字段以减小响应体积并提升性能
- 对列表使用分页 - 切勿请求无限制数据
- 优雅处理错误 - 同时检查数组和HTTP错误
errors - 为后台任务实现带指数退避的重试机制
- 使用片段处理跨查询的重复字段选择
- 优先使用GraphQL而非REST处理带关联关系的复杂查询