
Magento Graphql
- 63 installs
- 41 repo stars
- Updated March 13, 2026
- finsilabs/awesome-ecommerce-skills
Query Magento 2 / Adobe Commerce GraphQL for products, cart, checkout, and customer operations to build a headless or PWA Studio storefront.
About
Shows how to use Magento's /graphql endpoint for catalog queries, guest and customer carts, checkout, order history, and custom resolvers. A developer uses it when building a React/Vue/Next headless storefront or extending PWA Studio on Magento.
- Typed GraphQL client plus product, cart, auth, and place-order query examples
- Custom PHP resolver example and caching/store-header best practices
Magento Graphql by the numbers
- 63 all-time installs (skills.sh)
- Ranked #3,136 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Aug 3, 2026 (Skillselion catalog sync)
npx skills add https://github.com/finsilabs/awesome-ecommerce-skills --skill magento-graphqlAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 63 |
|---|---|
| repo stars | ★ 41 |
| Last updated | March 13, 2026 |
| Repository | finsilabs/awesome-ecommerce-skills ↗ |
What it does
Query Magento 2 / Adobe Commerce GraphQL for products, cart, checkout, and customer operations to build a headless or PWA Studio storefront.
Files
Magento GraphQL API
Overview
Magento 2 (and Adobe Commerce) ships a comprehensive GraphQL API for storefront operations — product catalog, search, cart, checkout, customer authentication, and order history. The endpoint is /graphql and does not require an admin token for public catalog data. Cart and customer operations require a guest cart ID (UUID) or a Bearer token from customer login. PWA Studio (Venia) is built entirely on this API, making it the reference implementation for headless Magento.
When to Use This Skill
- When building a React, Vue, or Next.js headless storefront on Magento 2
- When creating a native mobile app that needs Magento product and checkout data
- When implementing PWA Studio extensions that fetch custom data via GraphQL resolvers
- When replacing REST API calls with more efficient GraphQL queries in existing headless projects
- When building a custom GraphQL resolver to expose third-party or custom module data
- When integrating a headless CMS with Magento's product catalog
Core Instructions
1. Set up the GraphQL client
Magento GraphQL uses standard HTTP POST to /graphql. Use Apollo Client or a lightweight fetch wrapper:
// 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;
}2. Query the product catalog
// 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,
});
}3. Create guest cart and add items
// 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 });
}4. Authenticate customers and manage accounts
// 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);
}5. Create a custom GraphQL resolver in a Magento 2 module
<?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()];
}
}Examples
Product detail page — configurable product with swatches
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 });
}Checkout flow — set shipping address and place order
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 });
}Best Practices
- Use the `Store` HTTP header to target specific store views — without it, Magento defaults to the default store and returns incorrect pricing/currency for multi-store setups
- Enable GraphQL query caching in Magento via Varnish or built-in cache for public catalog queries — product list queries can be cached for minutes to reduce PHP execution
- Use inline fragments for polymorphic product types — always include
... on SimpleProduct,... on ConfigurableProduct,... on BundleProductwhere applicable - Persist the cart ID in a cookie — guest cart IDs (UUID) expire after 24 hours of inactivity; merge with customer cart after login using
mergeCartsmutation - Batch related queries — Apollo Client batching or manual query merging reduces RTTs for pages that need product + category + CMS block data simultaneously
- Implement proper error handling for `errors` array — GraphQL responses return 200 even for errors; always check
errorsproperty in the response body - Cache customer tokens securely — store bearer tokens in
httpOnlycookies, notlocalStorage, to prevent XSS token theft
Common Pitfalls
| Problem | Solution |
|---|---|
| Products missing from GraphQL but visible in Admin | Check the product's visibility attribute — products set to "Not Visible Individually" won't appear in catalog queries |
Configurable product variants return empty stock_status | Query the product field inside variants.product — stock is tracked at the simple product (variant) level, not the parent |
| Custom resolver not recognized | Run bin/magento setup:upgrade after adding new GraphQL schema files; also check schema.graphqls syntax carefully |
| Cart merge fails after customer login | Call mergeCarts(source_cart_id: $guestCartId, destination_cart_id: $customerCartId) — then discard the guest cart ID cookie |
| Slow GraphQL responses | Enable Magento's built-in GraphQL caching: bin/magento config:set system/full_page_cache/caching_application 1; use Varnish for public queries |
| Bearer token expired mid-session | Implement token refresh: catch The current customer isn't authorized error and redirect to login or use refresh token if available |
Related Skills
- @magento-module-development
- @magento-indexing-caching
- @magento-multi-store
- @headless-commerce-architecture
- @graphql-api-design
{
"context": "Tests whether the agent correctly implements the full Magento cart lifecycle: creating a guest cart, adding both simple and configurable products (with parent_sku), persisting the cart ID in a cookie, authenticating customers, using the correct query for the authenticated cart, and merging the guest cart after login.",
"type": "weighted_checklist",
"checklist": [
{
"name": "createEmptyCart mutation",
"max_score": 8,
"description": "Guest cart creation uses the `createEmptyCart` mutation (not a REST endpoint or other method)"
},
{
"name": "addSimpleProductsToCart mutation",
"max_score": 8,
"description": "Adding a simple product uses the `addSimpleProductsToCart` mutation"
},
{
"name": "addConfigurableProductsToCart mutation",
"max_score": 10,
"description": "Adding a configurable product uses the `addConfigurableProductsToCart` mutation (not `addSimpleProductsToCart`)"
},
{
"name": "parent_sku in configurable add",
"max_score": 10,
"description": "The `addConfigurableProductsToCart` call includes both `parent_sku` (the parent configurable SKU) AND the variant `sku` in `data`"
},
{
"name": "Cart ID in cookie",
"max_score": 9,
"description": "The guest cart ID (UUID) is stored and retrieved from a cookie — NOT from localStorage or a JavaScript variable only"
},
{
"name": "generateCustomerToken mutation",
"max_score": 8,
"description": "Customer login uses the `generateCustomerToken` mutation to obtain a bearer token"
},
{
"name": "customerCart query for auth cart",
"max_score": 10,
"description": "Fetching the authenticated customer's cart uses the `customerCart` query (not `cart(cart_id: ...)` with a cart ID parameter)"
},
{
"name": "mergeCarts mutation",
"max_score": 10,
"description": "After login, the guest cart is merged into the customer cart using the `mergeCarts` mutation with `source_cart_id` and `destination_cart_id`"
},
{
"name": "Guest cart cookie cleared after merge",
"max_score": 7,
"description": "After `mergeCarts`, the guest cart ID cookie is deleted/cleared"
},
{
"name": "Cart prices in response",
"max_score": 5,
"description": "Cart query responses include `prices` with `grand_total` or `subtotal_excluding_tax` fields"
},
{
"name": "Customer orders sort",
"max_score": 7,
"description": "If order history is fetched, it uses `sort: { sort_field: CREATED_AT, sort_direction: DESC }`"
},
{
"name": "Bearer token passed for auth requests",
"max_score": 8,
"description": "Authenticated requests (customerCart, orders) pass the bearer token in the Authorization header"
}
]
}
Shopping Cart and Login Integration for Headless Magento
Problem Description
A home furnishings brand has a Next.js headless storefront connected to Magento 2. Shoppers frequently browse while logged out, add items to their cart, and then log in at checkout — only to find their cart is empty. The current implementation creates a new cart session on login instead of carrying over the guest cart, causing significant cart abandonment.
Additionally, the team has reports that customers adding configurable products (sofas with selectable fabric/size) to the cart sometimes see the wrong variant or receive errors because the cart integration doesn't properly distinguish between the parent product and the chosen variant.
Your task is to build the cart and authentication module that resolves these issues.
Output Specification
Produce a TypeScript file lib/cart.ts containing functions for:
- Creating a new guest cart
- Adding a simple product to the cart
- Adding a configurable product (with a selected variant) to the cart
- Logging in a customer and handling the transition from guest cart to authenticated cart
- Fetching the current cart for display (both guest and authenticated scenarios)
- Fetching customer order history (most recent orders first)
Assume a magentoQuery helper is available (import from ./magento-client or stub it). Do not make live network calls — the code only needs to be structurally correct.
Also produce NOTES.md (max 25 lines) covering:
- How the guest-to-authenticated cart transition works and why it is necessary
- How configurable products are added differently from simple products
{
"context": "Tests whether the agent sets up a Magento GraphQL client with correct HTTP headers (including the Store header), proper error handling for the GraphQL errors array, Next.js ISR cache options, and secure token storage using httpOnly cookies rather than localStorage.",
"type": "weighted_checklist",
"checklist": [
{
"name": "Store header included",
"max_score": 12,
"description": "The fetch/request code includes a `Store` header (e.g. `Store: process.env...STORE_CODE` or similar) on every GraphQL request"
},
{
"name": "Store header default value",
"max_score": 8,
"description": "The Store header falls back to `\"default\"` when no store code env var is set (e.g. `?? \"default\"` or equivalent fallback)"
},
{
"name": "POST to /graphql",
"max_score": 8,
"description": "The client sends HTTP POST requests to the `/graphql` endpoint (not a REST path)"
},
{
"name": "GraphQL errors array check",
"max_score": 10,
"description": "The response handler checks for an `errors` property in the JSON body and throws or returns an error when it is non-empty"
},
{
"name": "HTTP error check",
"max_score": 6,
"description": "The code also checks `response.ok` (or HTTP status) and throws when the HTTP request itself fails"
},
{
"name": "Bearer token in Authorization header",
"max_score": 8,
"description": "When a token is provided, it is passed as `Authorization: Bearer <token>` in the request headers"
},
{
"name": "No token for public queries",
"max_score": 6,
"description": "The Authorization header is conditionally added only when a token is present — it is NOT sent on every request"
},
{
"name": "Next.js ISR revalidate",
"max_score": 8,
"description": "The fetch call includes `next: { revalidate: 300 }` (or a similar revalidation window around 300 seconds) in its options"
},
{
"name": "httpOnly cookie for token",
"max_score": 12,
"description": "The customer bearer token is stored or set in an httpOnly cookie — the code does NOT use `localStorage.setItem` or `sessionStorage` to store it"
},
{
"name": "No localStorage token storage",
"max_score": 8,
"description": "There is no `localStorage.setItem` or `sessionStorage.setItem` call used to persist the customer token"
},
{
"name": "Unauthorized error handling",
"max_score": 8,
"description": "The client or calling code handles the `The current customer isn't authorized` error by redirecting to login or clearing the token"
},
{
"name": "Content-Type header",
"max_score": 6,
"description": "The request includes `Content-Type: application/json` header"
}
]
}
Magento Headless Storefront: Core API Client
Problem Description
A fashion retailer is rebuilding their online store as a Next.js headless application backed by Magento 2. The backend Magento instance serves multiple regional storefronts (UK, DE, US) that have different currencies and pricing, so every API request must clearly identify which storefront it is targeting. The team wants public catalog pages to benefit from Next.js incremental static regeneration so pages automatically refresh without a full rebuild.
The retailer's security team conducted a review of the previous prototype and raised concerns about how authentication tokens were stored on the client side. The new implementation must address these concerns and follow current web security best practices for token storage.
Output Specification
Implement a TypeScript Magento GraphQL client module in a file named lib/magento-client.ts. It should export at minimum:
- A generic query/mutation function that handles all communication with the Magento GraphQL endpoint
- A customer login function that authenticates with email and password and stores the token securely
- A function (or middleware) that demonstrates how to make an authenticated request using the stored token
Also produce a short NOTES.md file (max 20 lines) that explains:
- How the store view targeting works
- How authentication tokens are stored and why
- How GraphQL error responses are handled
The implementation should be TypeScript and work within a Next.js App Router project. Use environment variables for the Magento URL and store code. Do not make live network calls — the code only needs to be structurally correct and runnable.
{
"context": "Tests whether the agent uses inline fragments for polymorphic Magento product types, includes aggregations for faceted filtering, correctly queries stock_status at the variant level for configurable products, and produces pagination-aware product list queries.",
"type": "weighted_checklist",
"checklist": [
{
"name": "SimpleProduct inline fragment",
"max_score": 9,
"description": "The product query includes a `... on SimpleProduct` inline fragment to access simple-product-specific fields"
},
{
"name": "ConfigurableProduct inline fragment",
"max_score": 9,
"description": "The product query includes a `... on ConfigurableProduct` inline fragment for configurable-product-specific fields"
},
{
"name": "Configurable options queried",
"max_score": 8,
"description": "Inside the `... on ConfigurableProduct` fragment, `configurable_options` is queried (e.g. attribute_code, label, values)"
},
{
"name": "Variant stock at product level",
"max_score": 12,
"description": "Stock status is queried on `variants.product` (the simple product inside the variant), NOT on the parent configurable product field directly"
},
{
"name": "Aggregations in product list query",
"max_score": 10,
"description": "The product list query includes an `aggregations` field (with at minimum attribute_code and options sub-fields) to support faceted filtering"
},
{
"name": "Pagination fields",
"max_score": 8,
"description": "The product list query includes `page_info` with at least `current_page` and `total_pages` (or `total_count`) for pagination"
},
{
"name": "pageSize and currentPage variables",
"max_score": 7,
"description": "The query accepts `$pageSize` and `$currentPage` as GraphQL variables (not hardcoded inline)"
},
{
"name": "Filter variable used",
"max_score": 7,
"description": "The query accepts a `$filter` variable of type `ProductAttributeFilterInput` (or equivalent) for category or attribute filtering"
},
{
"name": "Search variable used",
"max_score": 7,
"description": "The query accepts a `$search` variable to support keyword search on the `products` field"
},
{
"name": "Sort by position",
"max_score": 7,
"description": "The products query uses `sort: { position: ASC }` or includes a sort parameter"
},
{
"name": "Visibility note or handling",
"max_score": 8,
"description": "NOTES.md or code comments mention that products set to 'Not Visible Individually' will not appear in catalog queries"
},
{
"name": "Small image / thumbnail",
"max_score": 8,
"description": "Product query requests `small_image` or `thumbnail` with `url` and `label` for listing images"
}
]
}
Product Catalog Module for Headless Magento Storefront
Problem Description
An outdoor sports retailer runs a Magento 2 store with a broad product catalogue that includes both simple products (accessories, consumables) and configurable products (apparel with size/color variants). They are building a React-based headless storefront and need a product catalog module that powers two key pages: a category listing page with faceted filtering, and a product detail page.
The merchandising team has reported a recurring issue in staging: some products visible in the Magento admin panel do not show up when fetched through the API, and the stock status for clothing variants is always showing incorrectly. The new implementation should be designed so that these known data issues are handled correctly.
The listing page must support pagination and allow shoppers to filter by attributes (size, color, brand) using the facet counts returned by the API.
Output Specification
Produce a TypeScript file lib/catalog.ts containing:
- A
getProductsfunction for category/search listing with facets and pagination - A
getProductDetailfunction for a single product's full details (suitable for a product detail page)
Also produce a NOTES.md file (max 30 lines) that explains:
- How the code handles different product types in the response
- Why stock status is queried the way it is
- A note about the visibility issue and what causes products to be missing from API results
Do not make live network calls — assume a magentoQuery helper function is available (you may import it from ./magento-client or stub it). The code only needs to be structurally correct with proper GraphQL query strings.
{
"name": "finsi/magento-graphql",
"version": "0.1.0",
"summary": "Magento GraphQL API for headless storefronts and PWA Studio",
"skills": {
"magento-graphql": {
"path": "SKILL.md"
}
}
}