GraphQL API, customer token, and the comparison with Hyva SSR
Headless commerce separates the Magento backend from a standalone frontend via a GraphQL API, whether that frontend is a PWA, a native app, or an IoT terminal. This article provides a concrete decision framework: when the extra complexity pays off through multi-channel requirements and separate teams, and when Hyva as a server-rendered default setup remains the faster and cheaper choice.
Table of Contents
- 1. Context: what headless actually means for Magento
- 2. Architecture: GraphQL as the API layer
- 3. When the separation pays off
- 4. Extending the GraphQL schema: a custom module and resolver
- 5. Auth and customer token in a headless setup
- 6. SEO challenges with SPA frontends
- 7. Infrastructure and deployment
- 8. Cost versus benefit: when the investment doesn't pay off
- 9. Headless vs. Hyva SSR side by side
- 10. Summary
- 11. FAQ
1. Context: what headless actually means for Magento
Headless commerce means the Magento backend no longer serves HTML pages, it only provides data through an API, usually via GraphQL, sometimes complemented by REST endpoints. The actual presentation, meaning HTML, CSS and interaction logic, is handled by a completely separate application, for example a React- or Vue-based PWA, a native app, or a point-of-sale system. Backend and frontend communicate exclusively through the API, there is no shared codebase and no shared deployment cycle anymore.
The counter-model, which mironsoft uses as its default, is Hyva with server-side rendering. Hyva replaces the heavyweight Luma frontend with lean PHTML templates, Tailwind CSS, and Alpine.js for client-side interactivity, but stays within the classic Magento request-response cycle. The server renders complete HTML, the browser receives a finished page, and additional interactivity is added selectively through Alpine components. There is no separate frontend repository, no dedicated build and deploy process for the presentation layer, and no second runtime environment that needs to be operated.
Important: headless Magento is not an all-or-nothing decision. It is possible to run only the checkout or only a mobile app headless, while the main shop continues to be rendered server-side via Hyva. The GraphQL API exists in Magento regardless of whether you actively use it for a standalone frontend, since Hyva itself already uses GraphQL calls in the background for individual dynamic areas such as the mini cart slider.
2. Architecture: GraphQL as the API layer
The foundation of any headless commerce architecture with Magento is the GraphQL schema. Unlike REST, where every resource has its own endpoint, GraphQL provides a single endpoint under /graphql, through which the frontend requests exactly the fields it actually needs. The schema is assembled modularly from multiple schema.graphqls files, each Magento module can contribute its own types, queries and mutations, which are merged at runtime into a single overall schema.
For a frontend that needs to render product lists and a cart, the products and cart queries are the central entry points. The products query allows filtering, sorting, and pagination via arguments, while nested fields such as price_range or media_gallery are only loaded if they are actually requested in the query body. This eliminates the classic over-fetching problem of REST APIs, where the complete product record is always transferred even if the frontend only needs the name and price.
query ProductListWithCart($search: String!, $cartId: String!) {
products(search: $search, pageSize: 12, currentPage: 1) {
total_count
items {
sku
name
url_key
small_image { url label }
price_range {
minimum_price {
regular_price { value currency }
final_price { value currency }
}
}
}
page_info { current_page total_pages }
}
cart(cart_id: $cartId) {
id
total_quantity
prices {
grand_total { value currency }
}
items {
id
quantity
product { sku name }
}
}
}
The schema is strongly typed and explorable via introspection, tools like GraphiQL or Apollo Studio can read the complete Magento schema directly from the running system. For resolver developers this means: every new query or mutation must first be declared in the schema, before the PHP implementation behind it takes effect, so the schema is the binding contract between the backend team and every frontend client working against the API.
3. When the separation pays off
The strongest argument for headless Magento is multi-channel. Once a shop needs to serve not just a website but also a native iOS and Android app, a self-service terminal in a physical store, or a voice assistant integration, a central GraphQL API becomes the common denominator. Each channel implements its own presentation layer, but all of them access the same product data, pricing, and cart logic in the backend. Without this separation, every additional platform would have to build its own integrations against REST endpoints or directly against the database, which quickly leads to inconsistencies.
A second criterion is team structure. If a company already has an established frontend team with React or Next.js expertise that wants to release, test, and deploy independently of the Magento backend team, a monolithic Hyva architecture forces artificial coupling. With headless commerce, the frontend team can run its own sprints, set its own feature flags, and perform deployments independent of Magento release planning, as long as the GraphQL schema does not break as a contract.
Third, time-to-market for new touchpoints plays a role. Once the GraphQL API is established and sufficiently complete, an additional channel, such as a new landing page experience for a marketing campaign or a partner integration, costs noticeably less backend effort, because the business logic is already available through the API. The frontend team only builds a new client against an existing, stable schema, instead of coordinating backend changes anew for every new touchpoint.
4. Extending the GraphQL schema: a custom module and resolver
Once standard queries are not enough, for example because a frontend needs loyalty points or a custom stock level field, the schema is extended via a dedicated module. The declaration happens in a schema.graphqls file, which appends new fields to existing types or defines entirely new queries. The actual logic behind it lives in a resolver class that implements ResolverInterface and is bound to the schema via dependency injection.
<?php
declare(strict_types=1);
namespace Mironsoft\HeadlessApi\Model\Resolver;
use Magento\Framework\GraphQl\Config\Element\Field;
use Magento\Framework\GraphQl\Exception\GraphQlInputException;
use Magento\Framework\GraphQl\Exception\GraphQlNoSuchEntityException;
use Magento\Framework\GraphQl\Query\ResolverInterface;
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
use Mironsoft\LoyaltyApi\Api\LoyaltyPointsRepositoryInterface;
/**
* Resolves the loyalty point balance for a customer in a headless frontend.
*/
final class CustomerLoyaltyPoints implements ResolverInterface
{
/**
* @param LoyaltyPointsRepositoryInterface $loyaltyPointsRepository Service contract for loyalty balances
*/
public function __construct(
private readonly LoyaltyPointsRepositoryInterface $loyaltyPointsRepository,
) {
}
/**
* Resolves the loyaltyPoints field for the currently authenticated customer.
*
* @param Field $field Resolved schema field
* @param mixed $context Resolver context with customer id
* @param ResolveInfo $info GraphQL resolve metadata
* @param array|null $value Parent field value
* @param array|null $args Query arguments
* @return array{points: int, expires_at: string|null}
* @throws GraphQlInputException
* @throws GraphQlNoSuchEntityException
*/
public function resolve(
Field $field,
$context,
ResolveInfo $info,
?array $value = null,
?array $args = null,
): array {
$customerId = (int) ($context->getUserId() ?? 0);
if ($customerId === 0) {
throw new GraphQlInputException(__('A customer must be authenticated.'));
}
$balance = $this->loyaltyPointsRepository->getByCustomerId($customerId);
return [
'points' => $balance->getPoints(),
'expires_at' => $balance->getExpiresAt(),
];
}
}
The matching schema.graphqls file deliberately stays lean and only declares the contract, not the implementation:
type Customer {
loyaltyPoints: LoyaltyPoints @resolver(class: "Mironsoft\\HeadlessApi\\Model\\Resolver\\CustomerLoyaltyPoints")
}
type LoyaltyPoints {
points: Int!
expires_at: String
}
The resolver consistently uses PHP 8.4's constructor property promotion and accesses the actual business logic exclusively through the Service Contract interface LoyaltyPointsRepositoryInterface, never directly through a model or a collection. This keeps the resolver thin and testable, the actual business logic stays encapsulated in the repository implementation and can be unit-tested independently of the GraphQL layer.
5. Auth and customer token in a headless setup
In a classic Magento setup with Hyva, the customer session runs through a PHP session cookie that the browser sends automatically with every request. Once the frontend is a standalone application on a different domain or in a native app, this cookie model no longer works reliably, particularly because of cross-origin restrictions and because native apps do not share a browser cookie store at all. Headless commerce with Magento therefore relies on bearer token authentication via the generateCustomerToken mutation.
The frontend sends login credentials to this mutation and receives a token in return, which is sent with every further GraphQL request in the Authorization header. Magento validates the token server-side against the integration token table and derives the customer ID for the respective resolver context from it, entirely without a session cookie. For carts before login, a cart_id is additionally created for guest purchases, which is merged with the customer's cart after login via the mergeCarts mutation.
#!/usr/bin/env bash
# Step 1: authenticate and receive the bearer token
TOKEN=$(curl -s -X POST https://shop.mironsoft.de/graphql \
-H "Content-Type: application/json" \
-d '{
"query": "mutation { generateCustomerToken(email: \"customer@example.com\", password: \"secret\") { token } }"
}' | jq -r '.data.generateCustomerToken.token')
# Step 2: use the token for authenticated queries
curl -s -X POST https://shop.mironsoft.de/graphql \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${TOKEN}" \
-d '{
"query": "{ customer { firstname lastname email loyaltyPoints { points } } }"
}'
An important point in token management: standard customer tokens in Magento expire based on configuration, usually after a few hours, and have to be requested again by the frontend. For native apps, it is additionally advisable to add a refresh mechanism via a dedicated mutation, since a forced re-login on every token expiry leads to poor user experience in an app, whereas in a browser context with Hyva this is already handled transparently through the server-side session.
6. SEO challenges with SPA frontends
The biggest practical downside of headless commerce shows up in search engine optimization. A pure single-page application renders content client-side via JavaScript, after the initial HTML response was largely empty. Modern crawlers can execute JavaScript, but the crawl budget for it is limited, and rendering delays regularly mean that product details, prices, or availability are only indexed with a delay or not at all. With Hyva this problem disappears entirely, because the server already delivers complete, semantic HTML including content.
The common answer to this problem is server-side rendering or prerendering on the frontend side, for example via Next.js with getServerSideProps or a dedicated prerender middleware that detects crawler requests and serves finished HTML. But that means additional infrastructure and maintenance effort: a Node.js runtime has to be operated, scaled, and monitored, just to rebuild the same basic function Hyva already delivers server-side.
In addition, meta tags and structured data, which with Hyva come directly from the PHTML template and Magento attributes, have to be dynamically rebuilt per route from the GraphQL responses in the headless frontend. Title, description, canonical URL, Open Graph data, and product schema JSON-LD have to be set anew on every page change, usually via a head management library. If this step is missing, the page loses structured data and social sharing previews, without this being immediately obvious in the frontend itself.
7. Infrastructure and deployment
Headless commerce inevitably means two separate deployment pipelines. The Magento backend still goes through the classic Composer build with DI compile and static content deploy, while the frontend gets its own Node.js-based build and deploy process, usually on a CDN-adjacent platform like Vercel, Netlify, or a dedicated static hosting setup behind Cloudflare. Both pipelines have to be versioned and coordinated with each other, so a breaking schema change in the backend does not silently break an already deployed frontend build.
Because GraphQL requests run as POST requests by default, classic HTTP cache layers such as Varnish do not kick in automatically. For performance in a headless Magento setup, a dedicated caching layer in front of the GraphQL endpoint is therefore needed, either through persisted queries with GET requests and cache keys, or through a dedicated GraphQL edge cache that uses the query hash and store view as a cache key and is invalidated in a targeted way on product or price changes.
#!/usr/bin/env bash
set -euo pipefail
# Backend pipeline: Magento build and deploy
echo "[backend] composer install and DI compile"
bin/composer install --no-dev --optimize-autoloader
bin/magento setup:di:compile
bin/magento setup:static-content:deploy de_DE en_US -f
bin/magento cache:flush
# Frontend pipeline: separate Node.js build and static deploy
echo "[frontend] build headless client"
npm --prefix frontend ci
npm --prefix frontend run build
npm --prefix frontend run deploy:production
# Purge the GraphQL edge cache after both pipelines finished
echo "[edge-cache] purge GraphQL cache for affected store views"
curl -s -X POST https://edge-cache.mironsoft.de/purge \
-H "Authorization: Bearer ${EDGE_CACHE_TOKEN}" \
-d '{"tags": ["graphql", "store-view:de_DE", "store-view:en_US"]}'
A detail that is often underestimated: cache invalidation in the GraphQL edge cache has to be tied to the same Magento events that also trigger Varnish invalidation in a classic Hyva setup, meaning product saves, price changes, and stock updates. Without this coupling, the headless frontend shows stale prices even though the backend has long had current data.
8. Cost versus benefit: when the investment doesn't pay off
The extra effort of headless commerce is real and is regularly underestimated in early planning stages. Two systems mean two technology stacks, two deployment pipelines, two monitoring setups, and usually two teams with different skill profiles. A bug in price calculation can sit either in the GraphQL resolver or in the frontend formatting logic, which makes debugging and assigning blame across the system boundary harder.
For smaller shops with a single sales channel, meaning desktop and mobile web only, without a native app and without additional touchpoints, this complexity is not worth it in most cases. Hyva delivers the same modern, fast user experience with Tailwind and Alpine.js, without the SEO rework, without a second infrastructure, and without the need to build a frontend team alongside the backend team. The revenue increase from better Core Web Vitals scores, often cited as an argument for headless, can be achieved with Hyva too, because the server-rendered HTML is lean by design.
The decision for headless Magento should therefore never be a pure technology preference, it has to hinge on a concrete business need: multiple active channels, an already established and well-coordinated frontend team, or a product concept where commerce logic is explicitly meant to be embedded into several independently developed experience worlds. If one of these drivers is missing, the extra effort almost always outweighs the benefit.
9. Headless vs. Hyva SSR side by side
The table below summarizes the key decision criteria and maps them to a concrete recommendation, depending on which scenario actually applies to the project at hand.
| Criterion | Hyva SSR | Headless / GraphQL | Recommendation |
|---|---|---|---|
| Number of sales channels | One channel, web only | Web, native app, IoT, kiosk | Headless from 2+ real channels |
| SEO effort | Low, HTML comes from the server | High, SSR/prerender required | Hyva when SEO is a priority |
| Team structure | One team, PHP and Tailwind | Separate frontend/backend teams | Headless only with a real frontend team |
| Infrastructure effort | One pipeline, one deployment | Two pipelines, edge cache needed | Hyva for a small ops budget |
| Time-to-market for new touchpoints | New channel requires theme work | New client against existing schema | Headless for frequent new channels |
The common thread from the table: headless commerce wins exactly when multi-channel requirements, an established frontend organization, and a need for rapid expansion into new touchpoints actually exist. If these drivers are missing, Hyva SSR is the more pragmatic and cheaper solution on nearly every criterion, without any compromise on performance or user experience.
10. Summary
Headless Magento solves a concrete problem: multiple independent frontend channels that all need to access the same commerce logic, without each channel having to build its own backend integrations. The GraphQL API is the right foundation for this, with custom resolvers, Service Contracts, and customer token auth, the schema can be cleanly extended for project-specific requirements. Anyone taking this path also has to budget for the costs: separate deployment pipelines, a dedicated caching layer in front of GraphQL, and noticeably more SEO effort, because SPA frontends no longer deliver finished HTML.
For most Magento shops with a single channel, Hyva SSR remains the more pragmatic choice, faster to implement, cheaper to operate, and without the additional system boundary between frontend and backend. The decision for headless commerce should therefore always be derived from a real business need, multi-channel, separate teams, or high time-to-market requirements for new touchpoints, and not from a pure technology preference for modern JavaScript frameworks.
Headless Magento: the essentials at a glance
GraphQL as the contract
The schema assembled from multiple schema.graphqls files is the binding interface between the backend and every frontend client.
Customer token instead of cookie
Bearer token via generateCustomerToken replaces the PHP session once frontend and backend run on separate domains.
SEO needs extra effort
SPA frontends need SSR or prerendering plus dynamic meta and JSON-LD management per route.
Decide based on need
Headless pays off with multi-channel and separate teams, Hyva SSR remains the cheaper choice for single-channel shops.
11. FAQ: Headless Magento and GraphQL
1What does headless commerce mean for Magento?
2Is headless always better than Hyva SSR?
3Can you combine headless and Hyva?
4How do you extend the GraphQL schema?
5How does auth work without a session cookie?
6Why is SEO harder with SPA frontends?
7Does headless need its own caching layer?
8What role does team structure play?
9For which shops isn't headless worth it?
10What happens to the cart at login?
Mironsoft
Magento 2 development, GraphQL APIs and Hyva frontends
Headless commerce or Hyva SSR: which architecture fits your shop?
We analyze your channels, team structure, and growth plans, and build either a performant Hyva frontend or a GraphQL API with custom resolvers for a genuine headless setup, whichever actually makes sense for you.
Architecture consulting
Decision workshop: headless vs. Hyva SSR based on your channels and teams
GraphQL modules
Custom resolvers, schema extensions, and customer token auth following Service Contract conventions
Hyva frontends
Fast, server-rendered shops without additional frontend infrastructure