ARTICLE

Custom GraphQL Resolvers on Magento and Hyva: Extending the Storefront Schema Without REST

Custom GraphQL Resolvers on Magento and Hyva: Extending the Storefront Schema Without REST

A custom GraphQL resolver lets you add your own fields to the Magento storefront schema, so a Hyva theme can fetch exactly the data it needs in one typed query instead of a bolted-on REST call. It takes three pieces: a schema file, a resolver directive, and a PHP resolver class, plus a cache identity class to keep it fast.

Hyva fetches a lot of its storefront data client-side over GraphQL, so when you need custom data on the frontend, extending the GraphQL schema is the native way to deliver it. This guide covers the three parts of a resolver, the error classes to use, and the two things that separate a toy resolver from a production one: a cache identity class and batch resolvers. For the broader frontend, see our Hyva development services.

The three parts of a custom resolver

A storefront GraphQL extension in Magento is always the same shape:

  1. A schema file at etc/schema.graphqls in your module, where you add a field to type Query (or extend an existing type) and declare any custom types it returns.
  2. A @resolver directive on that field, pointing at the PHP class that resolves it, like @resolver(class: "\\Vendor\\Module\\Model\\Resolver\\MyField").
  3. A resolver class that implements Magento\Framework\GraphQl\Query\ResolverInterface and does the work.

The resolver class has one method, resolve(), which receives the Field, the context, the ResolveInfo, the parent $value, and the $args from the query. It returns data shaped to match the schema. Simple resolvers return an array directly; for deferred or batched values you wrap the result in a closure through a value factory. Adobe’s GraphQL resolvers documentation is the authoritative reference for the method signature and the return contract.

After adding or changing a schema file, flush the cache and recompile if needed, because the GraphQL schema is compiled from these files.

Errors and authorization

Do not throw generic PHP exceptions from a resolver. Magento has GraphQL-aware exception classes that produce a proper GraphQL error response the client can handle:

  • GraphQlAuthorizationException when the caller is not allowed to see the data, for example a logged-out customer requesting account data.
  • GraphQlNoSuchEntityException when the requested entity does not exist.

A typical resolver checks the context for a logged-in customer, throws an authorization exception if the request is not permitted, loads the data, and returns it in the schema’s shape. Using the right exception class matters because the storefront needs a structured error, not a 500, to respond gracefully. A Hyva component that calls a resolver and gets a clean GraphQL error can show the shopper a sensible message; one that gets a raw server error usually just breaks silently.

The part people skip: cache identity classes

Here is where most custom resolvers go wrong. A resolver that just returns data works, but it is not cacheable, so every storefront request runs the full resolve. On a Hyva store that calls GraphQL on many pages, that is a real cost.

To make a query cacheable, you add a cache identity class. It implements Magento\Framework\GraphQl\Query\Resolver\IdentityInterface, and its getIdentities() method returns one cache tag per entity in the result. You wire it to the field with the @cache directive in the schema, pointing at the identity class. The Magento_GraphQlResolverCache module then caches the resolver result at the resolver level, and the tags let Magento invalidate exactly the right cache entries when an entity changes.

The pattern, from Adobe’s identity class documentation, is to emit a tag per returned entity (a prefix plus the entity ID) and a general prefix tag. Get this right and a custom field is cached and invalidated as cleanly as a native product query. Skip it and the field is a permanent cache miss. Our guide to GraphQL performance on a Hyva Magento storefront covers how this caching fits the wider performance picture, including persisted queries and GET-cacheable requests.

Avoiding N+1 with batch resolvers

The other production concern is the N+1 problem. If a query returns a list and your resolver fires one database call per item in the list, a page with 50 products becomes 50 queries. The fix is batching.

Magento provides BatchResolverInterface, which collects requests for the same field across the list and resolves them in a single operation. There is also BatchServiceContractResolverInterface, which does the same but delegates the work to a service contract, keeping your data access in the service layer. Adobe points to the linked-products resolver in the RelatedProductGraphQl module as a reference implementation worth reading.

Concern Naive resolver Production resolver
Caching Not cacheable, every request resolves Identity class plus @cache directive
List performance One query per item (N+1) Batch resolver, one query per field
Errors Generic exception, 500 to client GraphQL exception classes
Invalidation None Cache tags per entity

One subtlety: when you batch, the identity class still has to see the full batched result set so it can emit a tag for every entity returned. Batching and caching are designed to work together, not in isolation.

A resolver, start to finish

To make the shape concrete, picture adding a field that returns a product’s lead time from a custom attribute. The pieces line up like this:

  • Schema. In schema.graphqls, extend the product type: leadTimeDays: Int @resolver(class: "\\Vendor\\Module\\Model\\Resolver\\LeadTime"). Extending an existing type means the field appears on every product query, so the storefront can request it inline.
  • Resolver. The LeadTime class implements ResolverInterface. Its resolve() reads the product from the parent $value, pulls the attribute, and returns the integer. Because this field hangs off a list of products, you build it as a batch resolver so a 50-product grid does not fire 50 attribute loads.
  • Identity. The cache identity emits a tag per product ID so that editing a product’s lead time invalidates only the cached fields for that product, not the whole query.

That is the full mental model: declare it in the schema, resolve it in PHP, batch it for lists, and tag it for the cache. Everything else is detail. The same three-part structure scales from a single scalar field to a complex custom type that joins data the native schema never exposed, which is exactly what makes resolvers worth the care they take.

When a resolver is the right tool

A custom resolver is the right answer when the storefront needs custom data in its own typed query. It is not the only tool, so choose deliberately:

  • Custom resolver when Hyva or another storefront client needs the data inline in a GraphQL query, cached and typed.
  • REST endpoint when the consumer is a server-to-server integration that does not use the storefront GraphQL schema.
  • Out-of-process extensibility when the logic belongs outside Magento entirely. Our guide to Adobe Commerce webhooks and out-of-process extensibility covers when to move logic off the core.

For connecting a Hyva frontend to enterprise systems, resolvers often sit alongside API orchestration. Our guide to wiring a Hyva frontend to enterprise systems with GraphQL and API Mesh covers the architecture when the data spans more than Magento. Adobe’s GraphQL developer guide is the entry point for the whole schema.

How storefront APIs compare across platforms

Typed storefront APIs are now standard, but the extension model differs:

What is specific to Magento is that you extend the schema with server-side PHP resolvers that run inside the application, with full access to its services, cache tags, and batching. That is powerful and it is yours to get right. The team at Bemeir builds these resolvers on production Hyva stores, and our technology partners cover the systems they often pull data from. Learn how we work about Bemeir, and see our Magento development services for the backend behind every query.

FAQ

How do I add a custom field to the Magento GraphQL schema?

Create an etc/schema.graphqls file in your module, add the field to type Query or extend an existing type, and annotate it with a @resolver directive pointing at your PHP class. Implement that class against ResolverInterface with a resolve() method that returns data in the schema’s shape, then flush the cache so the schema recompiles.

What interface does a Magento GraphQL resolver implement?

Magento\Framework\GraphQl\Query\ResolverInterface, which defines a single resolve() method. It receives the field, context, resolve info, the parent value, and the query arguments, and returns data shaped to the schema. For batched or deferred results you can return a closure through a value factory instead of a plain array.

How do I make a custom GraphQL query cacheable?

Add a cache identity class implementing IdentityInterface, whose getIdentities() returns a cache tag per entity in the result, and wire it with the @cache directive in your schema. The resolver cache module then caches the result at the resolver level and invalidates it by tag when an entity changes. Without an identity class, the query is never cached.

How do I avoid N+1 queries in a GraphQL resolver?

Use a batch resolver. BatchResolverInterface collects requests for the same field across a list and resolves them in one operation, instead of one database call per item. BatchServiceContractResolverInterface does the same while delegating to a service contract. The cache identity class must still see the full batched result to tag every entity.

Should I use a GraphQL resolver or a REST endpoint?

Use a custom resolver when a storefront client like Hyva needs the data inline in its GraphQL query, cached and typed. Use REST for server-to-server integrations that do not consume the storefront schema, and consider out-of-process extensibility when the logic belongs outside Magento entirely.

Extend the schema the right way

Custom resolvers are how a Hyva storefront gets exactly the data it needs, but a resolver without a cache identity class or batching is a performance problem waiting to surface. Doing it properly means treating caching and batching as part of the resolver, not an afterthought. Bemeir is the USA’s first Hyva Gold Partner and an Adobe Commerce shop that builds storefront APIs that stay fast at scale, and we treat the cache identity class and batching as non-negotiable parts of every resolver we ship.

Let us help you get started on a project with Custom GraphQL Resolvers on Magento and Hyva: Extending the Storefront Schema Without REST and leverage our partnership to your fullest advantage. Fill out the contact form below to get started.

more articles about ecommerce

Read on the latest with Shopify, Magento, eCommerce topics and more.