shopify-admin-graphql について
shopify-admin-graphqlは、GraphQL経由でShopify Admin APIを利用するコード向けの開発スキルです。アプリやバックグラウンドジョブで使う認証、一般的なクエリ、ページネーション、スロットリング、エラー処理を扱います。
主な機能
- 01Remix認証を通じたAPIアクセス
- 02オンライン・オフラインの管理セッション
- 03ショップ、商品、顧客、注文のクエリ
- 04大規模データセットのページネーション
- 05スロットリングとGraphQLエラーの処理
- 06GraphQLクエリのベストプラクティス
活用シーン
- 01Remix loaderでShopifyデータをクエリ
- 02GraphQL mutationでShopifyのレコードを更新
- 03リクエストコンテキストなしでWebhookを処理
- 04ページネーションとリトライで大量データを取得
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ tamiror6/ shopify-app-skills --skill shopify-admin-graphqlインストール後、Claude Code で/shopify-admin-graphqlを入力して読み込まれたか確認します。
詳細
Shopify Admin GraphQL
Shopify Admin API と GraphQL 経由で通信するコードを追加または変更する際は、このスキルを使用してください。
使用する場面
- Shopify のデータ(ショップ、顧客、注文、商品、在庫)をクエリする場合
- Shopify のデータ(顧客、注文、商品の作成や更新)を変更する場合
- 大規模なデータセットのページネーションを実装する場合
- API のスロットリングやレート制限に対応する場合
- メタフィールドやその他の Shopify リソースを扱う場合
GraphQL クライアントの取得
Remix のローダー/アクション内(推奨)
@shopify/shopify-app-remix の authenticate.admin() を使用してください。
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 });
};
変数を使用する場合
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 });
};
バックグラウンドジョブ/Webhook(オフラインアクセス)
リクエストコンテキストがない場合は、オフラインセッショントークンを使用してください。
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;
}
よくあるクエリパターン
ショップの詳細
query GetShopDetails {
shop {
id
name
email
myshopifyDomain
primaryDomain {
url
}
plan {
displayName
}
currencyCode
timezoneAbbreviation
}
}
ページネーションを使用した商品一覧
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
}
}
}
}
}
}
}
顧客の検索
query FindCustomer($query: String!) {
customers(first: 1, query: $query) {
edges {
node {
id
email
phone
firstName
lastName
ordersCount
totalSpent
}
}
}
}
ID による注文の取得
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
}
}
}
スロットリングへの対応
Shopify では、レート制限にリーキーバケットアルゴリズムを使用しています。一括操作やバックグラウンドジョブでは、リトライロジックを実装してください。
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");
}
エラー処理
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
}
}
ベストプラクティス
- デバッグしやすいよう、オペレーション名を付けてください:
query GetShopDetails { ... } - レスポンスサイズを減らし、パフォーマンスを向上させるため、必要なフィールドだけをリクエストしてください
- リストにはページネーションを使用してください。上限のないデータをリクエストしないでください
- エラーを適切に処理してください。
errors配列と HTTP エラーの両方を確認してください - バックグラウンドジョブには、指数バックオフを伴うリトライを実装してください
- クエリ間でフィールド選択が繰り返される場合は、フラグメントを使用してください
- 関連データを含む複雑なクエリでは、REST より GraphQL を優先してください
参考資料
コメント
Markdown 対応