magento-graphql について
ヘッドレスストアやアプリでMagento 2とAdobe CommerceのGraphQLを利用する開発スキルです。Magentoを基盤に開発するチーム向けに、商品検索、カートや顧客操作、カスタムリゾルバーを扱います。
主な機能
- 01GraphQLクライアントの設定
- 02商品カタログのクエリ
- 03ゲストカート操作
- 04顧客認証
- 05カスタムリゾルバー開発
活用シーン
- 01ヘッドレスMagentoストアの構築
- 02モバイルアプリでのMagentoデータ利用
- 03カートとチェックアウトの管理
- 04カスタムモジュールデータの公開
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ finsilabs/ awesome-ecommerce-skills --skill magento-graphqlインストール後、Claude Code で/magento-graphqlを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
tile.jsonevals/cart-lifecycle-and-customer-authenticati/criteria.jsontask.mdevals/graphql-client-setup-and-auth-security/criteria.jsontask.mdevals/product-catalog-queries-with-polymorphic/criteria.jsontask.md詳細
Magento GraphQL API
概要
Magento 2(および Adobe Commerce)には、ストアフロントでの操作に対応する包括的な GraphQL API が標準搭載されています。商品カタログ、検索、カート、チェックアウト、顧客認証、注文履歴などを扱えます。エンドポイントは /graphql で、公開カタログデータの取得に管理者トークンは必要ありません。カートや顧客関連の操作には、ゲストカート ID(UUID)または顧客ログインで取得した Bearer トークンが必要です。PWA Studio(Venia)はこの API のみを使って構築されているため、ヘッドレス Magento のリファレンス実装となっています。
このスキルを使用する場面
- Magento 2 で React、Vue、または Next.js を使ったヘッドレスストアフロントを構築するとき
- Magento の商品データやチェックアウトデータを必要とするネイティブモバイルアプリを作成するとき
- GraphQL リゾルバーを使ってカスタムデータを取得する PWA Studio 拡張機能を実装するとき
- 既存のヘッドレスプロジェクトで、REST API 呼び出しをより効率的な GraphQL クエリに置き換えるとき
- サードパーティーやカスタムモジュールのデータを公開するカスタム GraphQL リゾルバーを構築するとき
- ヘッドレス CMS を Magento の商品カタログと連携するとき
基本的な手順
-
GraphQL クライアントをセットアップする
Magento GraphQL では、標準の HTTP POST を使って
/graphqlにリクエストを送信します。Apollo Client または軽量な fetch ラッパーを使用します。// lib/magento.ts const MAGENTO_URL = process.env.NEXT_PUBLIC_MAGENTO_URL; // e.g. https://magento.example.com export async function magentoQuery<T>( query: string, variables: Record<string, unknown> = {}, token?: string ): Promise<T> { const headers: Record<string, string> = { "Content-Type": "application/json", Store: process.env.NEXT_PUBLIC_MAGENTO_STORE_CODE ?? "default", }; if (token) { headers["Authorization"] = `Bearer ${token}`; } const response = await fetch(`${MAGENTO_URL}/graphql`, { method: "POST", headers, body: JSON.stringify({ query, variables }), next: { revalidate: 300 }, // Next.js ISR caching }); if (!response.ok) { throw new Error(`Magento GraphQL HTTP error: ${response.status}`); } const { data, errors } = await response.json(); if (errors?.length) { throw new Error(errors[0].message); } return data as T; } -
商品カタログをクエリする
// Fetch products by category UID or search export async function getProducts(params: { categoryUid?: string; search?: string; pageSize?: number; currentPage?: number; }) { return magentoQuery<{ products: MagentoProductList }>(` query GetProducts( $search: String $filter: ProductAttributeFilterInput $pageSize: Int $currentPage: Int ) { products( search: $search filter: $filter pageSize: $pageSize currentPage: $currentPage sort: { position: ASC } ) { total_count page_info { current_page page_size total_pages } aggregations { attribute_code label count options { label value count } } items { uid sku name url_key ... on SimpleProduct { price_range { minimum_price { regular_price { value currency } final_price { value currency } discount { percent_off amount_off } } } } ... on ConfigurableProduct { configurable_options { attribute_code label values { uid label swatch_data { value } } } variants { attributes { uid label code value_index } product { sku stock_status price_range { minimum_price { final_price { value } } } } } } small_image { url label } thumbnail { url label } rating_summary review_count stock_status } } } `, { search: params.search, filter: params.categoryUid ? { category_uid: { eq: params.categoryUid } } : undefined, pageSize: params.pageSize ?? 20, currentPage: params.currentPage ?? 1, }); } -
ゲストカートを作成して商品を追加する
// Create a guest cart and get the cart ID export async function createGuestCart(): Promise<string> { const data = await magentoQuery<{ createEmptyCart: string }>(` mutation { createEmptyCart } `); return data.createEmptyCart; // Returns a UUID cart ID } // Add a simple product to cart export async function addSimpleProductToCart(cartId: string, sku: string, qty: number) { return magentoQuery(` mutation AddSimpleProduct($cartId: String!, $sku: String!, $qty: Float!) { addSimpleProductsToCart( input: { cart_id: $cartId cart_items: [{ data: { sku: $sku, quantity: $qty } }] } ) { cart { items { uid quantity product { name sku } prices { price { value currency } } } prices { subtotal_excluding_tax { value currency } grand_total { value currency } } } } } `, { cartId, sku, qty }); } // Add a configurable product with selected variant export async function addConfigurableProductToCart( cartId: string, parentSku: string, variantSku: string, qty: number ) { return magentoQuery(` mutation AddConfigurable($cartId: String!, $parentSku: String!, $variantSku: String!, $qty: Float!) { addConfigurableProductsToCart( input: { cart_id: $cartId cart_items: [{ parent_sku: $parentSku data: { sku: $variantSku, quantity: $qty } }] } ) { cart { items { uid quantity product { name } } } } } `, { cartId, parentSku, variantSku, qty }); } -
顧客を認証してアカウントを管理する
// Customer login — returns a bearer token export async function loginCustomer(email: string, password: string): Promise<string> { const data = await magentoQuery<{ generateCustomerToken: { token: string } }>(` mutation Login($email: String!, $password: String!) { generateCustomerToken(email: $email, password: $password) { token } } `, { email, password }); return data.generateCustomerToken.token; } // Get authenticated customer's cart export async function getCustomerCart(token: string) { return magentoQuery(` query { customerCart { id items { uid quantity product { name sku small_image { url } } prices { price { value currency } } } prices { grand_total { value currency } } } } `, {}, token); } // Get order history export async function getCustomerOrders(token: string, pageSize = 10) { return magentoQuery(` query GetOrders($pageSize: Int) { customer { orders(pageSize: $pageSize, sort: { sort_field: CREATED_AT, sort_direction: DESC }) { total_count items { id number status order_date total { grand_total { value currency } } items { product_name product_sku quantity_ordered } shipping_address { firstname lastname city region { code } country_code } } } } } `, { pageSize }, token); } -
Magento 2 モジュールにカスタム GraphQL リゾルバーを作成する
<?php // app/code/MyVendor/CustomGraphQL/etc/schema.graphqls type Query { myCustomProducts( brand: String @doc(description: "Filter by brand") ): MyCustomProductOutput @resolver(class: "MyVendor\\CustomGraphQL\\Model\\Resolver\\CustomProducts") @doc(description: "Get custom product list") } type MyCustomProductOutput { items: [CustomProductItem] total_count: Int } type CustomProductItem { sku: String name: String brand: String custom_attribute: String }<?php // app/code/MyVendor/CustomGraphQL/Model/Resolver/CustomProducts.php namespace MyVendor\CustomGraphQL\Model\Resolver; use Magento\Framework\GraphQl\Config\Element\Field; use Magento\Framework\GraphQl\Query\ResolverInterface; use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory; class CustomProducts implements ResolverInterface { public function __construct( private readonly CollectionFactory $collectionFactory ) {} public function resolve(Field $field, $context, ResolveInfo $info, array $value = null, array $args = null) { $collection = $this->collectionFactory->create(); $collection->addAttributeToSelect(['sku', 'name', 'brand', 'custom_attribute']); $collection->addAttributeToFilter('status', 1); $collection->addAttributeToFilter('visibility', ['neq' => 1]); if (!empty($args['brand'])) { $collection->addAttributeToFilter('brand', ['like' => '%' . $args['brand'] . '%']); } $items = []; foreach ($collection as $product) { $items[] = [ 'sku' => $product->getSku(), 'name' => $product->getName(), 'brand' => $product->getData('brand'), 'custom_attribute' => $product->getData('custom_attribute'), ]; } return ['items' => $items, 'total_count' => $collection->getSize()]; } }
例
商品詳細ページ — スウォッチ付きの設定可能な商品
export async function getProductByUrlKey(urlKey: string) {
return magentoQuery<{ products: { items: MagentoProduct[] } }>(`
query GetProduct($urlKey: String!) {
products(filter: { url_key: { eq: $urlKey } }) {
items {
uid sku name url_key
meta_title meta_description
description { html }
short_description { html }
... on ConfigurableProduct {
configurable_options {
id uid label attribute_code position
values {
uid label
swatch_data {
... on ColorSwatchData { value }
... on ImageSwatchData { thumbnail value }
... on TextSwatchData { value }
}
}
}
variants {
attributes { uid label code value_index }
product {
uid sku stock_status
media_gallery { url label disabled }
price_range {
minimum_price {
regular_price { value currency }
final_price { value currency }
}
}
}
}
}
media_gallery { url label disabled position }
reviews(pageSize: 5) {
items {
summary text created_at
ratings_breakdown { name value }
nickname
}
}
}
}
}
`, { urlKey });
}
チェックアウトフロー — 配送先住所を設定して注文する
export async function setShippingAddress(cartId: string, address: AddressInput) {
return magentoQuery(`
mutation SetShipping($cartId: String!, $address: CartAddressInput!) {
setShippingAddressesOnCart(input: {
cart_id: $cartId
shipping_addresses: [{ address: $address }]
}) {
cart {
shipping_addresses {
available_shipping_methods {
carrier_code method_code carrier_title method_title
amount { value currency }
}
}
}
}
}
`, { cartId, address });
}
export async function placeOrder(cartId: string) {
return magentoQuery<{ placeOrder: { order: { order_number: string } } }>(`
mutation PlaceOrder($cartId: String!) {
placeOrder(input: { cart_id: $cartId }) {
order { order_number }
errors { message code }
}
}
`, { cartId });
}
ベストプラクティス
StoreHTTP ヘッダーを使用して特定のストアビューを指定します。指定しない場合、Magento はデフォルトのストアを使用するため、複数のストアを設定している環境では価格や通貨が正しく返されません- Varnish または組み込みキャッシュを使って Magento の GraphQL クエリキャッシュを有効にします。公開カタログのクエリを数分間キャッシュすると、PHP の実行を減らせます
- ポリモーフィックな商品タイプにはインラインフラグメントを使用します。該当する場合は、常に
... on SimpleProduct、... on ConfigurableProduct、... on BundleProductを含めます - カート ID を Cookie に保存します。ゲストカート ID(UUID)は、24 時間操作がないと期限切れになります。ログイン後は
mergeCartsミューテーションを使って顧客カートと統合します - 関連するクエリをまとめます。Apollo Client のバッチ処理または手動でのクエリ統合により、商品、カテゴリ、CMS ブロックのデータを同時に必要とするページで RTT を減らせます
errors配列を適切に処理します。GraphQL はエラーが発生しても 200 を返すため、レスポンス本文のerrorsプロパティを必ず確認します- 顧客トークンを安全にキャッシュします。XSS によるトークン窃取を防ぐため、Bearer トークンは
localStorageではなくhttpOnlyCookie に保存します
よくある問題
| 問題 | 解決策 |
|---|---|
| GraphQL には表示されない商品が管理画面には表示される | 商品の visibility 属性を確認します。「Not Visible Individually」に設定された商品はカタログクエリに表示されません |
設定可能な商品のバリアントで stock_status が空になる | variants.product 内の product フィールドをクエリします。在庫は親商品ではなく、シンプル商品(バリアント)単位で管理されます |
| カスタムリゾルバーが認識されない | GraphQL スキーマファイルを追加した後に bin/magento setup:upgrade を実行します。また、schema.graphqls の構文も慎重に確認してください |
| 顧客ログイン後のカート統合に失敗する | mergeCarts(source_cart_id: $guestCartId, destination_cart_id: $customerCartId) を呼び出し、その後ゲストカート ID の Cookie を削除します |
| GraphQL のレスポンスが遅い | Magento の組み込み GraphQL キャッシュを有効にします:bin/magento config:set system/full_page_cache/caching_application 1。公開クエリには Varnish を使用します |
| セッション中に Bearer トークンの期限が切れる | トークンの更新を実装します。The current customer isn't authorized エラーを捕捉し、ログイン画面にリダイレクトするか、利用可能な場合はリフレッシュトークンを使用します |
Related Skills
- @magento-module-development
- @magento-indexing-caching
- @magento-multi-store
- @headless-commerce-architecture
- @graphql-api-design
コメント
Markdown 対応