Structuring Reusable GraphQL Fragments in a Hyvä Theme
AI generated
Hyvä
phtml
Hyvä Theme
Reusable GraphQL Fragments
structured cleanly in a Hyvä theme

Rewriting the same product fields in every query does not just bloat the source code, it bloats every single request sent to the server. Fragment composition keeps GraphQL queries in a Hyvä frontend lean, consistent, and maintainable across Magento upgrades.

12 min read GraphQL Fragments Query Composition

1. The problem: duplicated fields in every product query

In a Hyvä theme that has grown over time, the product list, cart, wishlist, and cross-sell slider almost always request the same basic fields: SKU, name, image, price range, and URL key. When those fields are spelled out individually in every query file, every new call site adds another risk that a change gets applied in one place and forgotten everywhere else.

The consequences usually only show up in production: a special price missing from the wishlist because its query was never updated to include special_price, or price formatting that drifts slightly between the category page and the cart because both were maintained independently. Maintenance cost does not scale with the number of fields, it scales with the number of places where the same fields were retyped.

2. GraphQL fragment basics, briefly revisited

A named fragment defines a field selection once for a given type and can then be spread into as many queries as needed. For Magento that means concretely: a fragment called ProductCardFields on ProductInterface describes exactly the fields a product card needs, and every query that renders a product card spreads that one fragment instead of declaring the fields again.

Magento's GraphQL schema supports fragments like any spec-compliant GraphQL server, with no special handling required. That works regardless of whether the Hyvä frontend uses a lean fetch-based GraphQL client or a more elaborate library with query caching, because fragments are pure query-document syntax that gets assembled into a complete query before it is ever sent.


fragment ProductCardFields on ProductInterface {
  sku
  name
  url_key
  small_image {
    url
    label
  }
  price_range {
    minimum_price {
      regular_price { value currency }
      final_price { value currency }
    }
  }
}

query CategoryProducts($categoryId: String!) {
  products(filter: { category_id: { eq: $categoryId } }) {
    items {
      ...ProductCardFields
    }
  }
}

query WishlistItems($wishlistId: ID!) {
  wishlist(id: $wishlistId) {
    items_v2 {
      items {
        product {
          ...ProductCardFields
        }
      }
    }
  }
}

3. Layered fragment composition: price, card, and detail fragments

In practice, a layered structure pays off over one single large fragment: a small ProductPriceFields fragment encapsulates only the pricing logic, ProductCardFields spreads that price fragment and adds image and name, and ProductDetailFields in turn builds on ProductCardFields and adds description plus customer-specific attributes. If a price field changes, say because a new tier-price field gets added, one change in exactly one place takes effect everywhere prices are displayed.

This layering deliberately mirrors the structure of the Hyvä templates: a price.phtml partial renders the same fields the price fragment supplies, and a product-card.phtml spreads that partial, just as the corresponding query spreads the price fragment. When template boundaries and fragment boundaries line up, both can be reasoned about together instead of maintaining separate mental models for markup and data fetching.


fragment ProductPriceFields on ProductInterface {
  price_range {
    minimum_price {
      regular_price { value currency }
      final_price { value currency }
      discount { percent_off }
    }
  }
}

fragment ProductCardFields on ProductInterface {
  sku
  name
  url_key
  small_image { url label }
  ...ProductPriceFields
}

fragment ProductDetailFields on ProductInterface {
  ...ProductCardFields
  description { html }
  meta_description
  categories { name url_path }
}

4. Organizing fragments centrally instead of inline in every query file

Instead of redefining fragments in every query file, a dedicated directory for reusable fragments, from which every query imports exactly what it needs, tends to work well. The Hyvä GraphQL client assembles the final query from the main document and the imported fragments, so exactly one complete request goes to the server in the end, while the source code itself stays modular and individual fragments can be tested independently.

One pitfall here is naming collisions: if two teams independently define a fragment called ProductFields with different contents, assembling the query produces a conflict that only shows up as a cryptic runtime error. A feature-prefixed naming convention, such as PlpProductCardFields versus CheckoutProductFields, reliably prevents such collisions even when several teams work on different areas of the theme in parallel.


// app/design/frontend/Vendor/hyva-child/web/js/graphql/fragments/product-card.js
export const PLP_PRODUCT_CARD_FIELDS = /* GraphQL */ `
  fragment PlpProductCardFields on ProductInterface {
    sku
    name
    url_key
    small_image { url label }
  }
`;

// app/design/frontend/Vendor/hyva-child/web/js/graphql/queries/category-products.js
import { PLP_PRODUCT_CARD_FIELDS } from '../fragments/product-card';

export const CATEGORY_PRODUCTS_QUERY = /* GraphQL */ `
  ${PLP_PRODUCT_CARD_FIELDS}
  query CategoryProducts($categoryId: String!) {
    products(filter: { category_id: { eq: $categoryId } }) {
      items { ...PlpProductCardFields }
    }
  }
`;

5. Reducing query size and network overhead through fragments

Fragment reuse pays off primarily not through better compression, but through the fact that every single query contains only the fields that particular view actually needs. A wishlist that spreads only the lean ProductCardFields fragment transfers noticeably less data than a query that accidentally drags along the full ProductDetailFields fragment with description and category tree, just because it already existed somewhere else.

With persisted queries, fragment discipline pays off further: a manageable, clearly layered set of query documents is far easier to check and maintain against a server-side allow-list than dozens of nearly identical queries that grew independently and quietly drifted apart over time.

6. Fragments and client-side caching in the Hyvä frontend

Normalized client-side caching depends on the same entity, say a product with a given SKU, coming back with the same field shape at every call site. Fragments guarantee exactly that: if the same ProductCardFields fragment is used everywhere, cache entries for the same SKU can be merged losslessly, instead of the card, wishlist, and cart each storing a slightly different partial slice of the same entity in the cache.

In a Hyvä frontend, a cache like that can be implemented pragmatically as an Alpine store that indexes products by SKU and checks, on read, whether a complete entry already exists before triggering a new request. Because the field shape is guaranteed consistent by the fragment, a simple object merge is enough, without needing a more elaborate normalization library.


// web/js/graphql/product-cache.js
const productCache = {};

export function mergeProduct(product) {
  productCache[product.sku] = { ...productCache[product.sku], ...product };
  return productCache[product.sku];
}

export function getCachedProduct(sku) {
  return productCache[sku] || null;
}

7. Versioning fragments across schema changes between Magento upgrades

Magento minor releases occasionally shift fields in the GraphQL schema, for instance when the price_range structure gets extended with a new tier-price field, or a previously used field is marked deprecated and later removed. If all product fields live in a handful of central, layered fragments, the fix happens in exactly that one place instead of hunting through dozens of files for scattered occurrences.

For larger breaking changes, a temporary parallel fragment version, such as ProductCardFieldsV2 alongside the existing ProductCardFields, is worth introducing, so individual views can be migrated and tested one at a time before the old version is removed. A short comment block at the top of every fragment file documenting the minimum supported Magento version saves the tedious work of figuring out when a given field was even introduced during future upgrades.


# Compatible from Magento 2.4.6, price_range.minimum_price without tier_prices
fragment ProductCardFields on ProductInterface {
  sku
  name
  price_range {
    minimum_price {
      regular_price { value currency }
      final_price { value currency }
    }
  }
}

# From Magento 2.4.8, adds price_tiers for tier price display
fragment ProductCardFieldsV2 on ProductInterface {
  sku
  name
  price_range {
    minimum_price {
      regular_price { value currency }
      final_price { value currency }
    }
  }
  price_tiers {
    quantity
    final_price { value currency }
  }
}

8. Testing and schema validation for fragments in the CI pipeline

An introspection query against your own Magento instance returns the full schema actually available right now, as a reference. A small script that checks every field selection in your own fragments against that introspected schema catches breaking changes before a Composer update of magento/module-graph-ql lands untested in production and only surfaces through broken responses.

If that check runs as its own step in the CI pipeline before every deployment, the build fails reliably the moment a fragment references a field that no longer exists in the target schema. That turns a potential production bug into a clearly readable error message inside the pull request, long before a customer ever sees a broken product card.

9. Checklist for a maintainable fragment structure

Fragment composition is not an end in itself, it pays off mainly when the team consistently follows a clear convention for location, naming, and layering. A single undisciplined, ever-growing mega-fragment that accidentally contains every field ever needed causes the same problems as having no fragments at all, just concentrated in one place that is even harder to untangle.

The overview below ranks the measures discussed in this article by impact and implementation effort, so a team can focus first on the points that promise the biggest effect for a manageable amount of work.

Measure Impact Implementation Effort Risk of Doing Nothing
Layered fragments instead of a mega-fragment Very high Medium, one-time restructuring Unnecessarily large queries at every call site
Central fragment directory with imports High Low to medium Duplicated field lists that slowly drift apart
Feature-prefixed naming convention Medium Low Naming collisions between teams when assembling queries
Fragment cache in the Alpine store Medium Medium Redundant requests for already-loaded products
Versioned fragments for breaking changes High Medium, during the migration window A big-bang migration instead of a gradual rollout
Schema validation in the CI pipeline Very high Low, a one-time script Breaking changes only surface live through customer reports

Mironsoft

Hyvä theme development and Luma migration

Still running Luma, or a Hyvä theme that just doesn't feel right?

We build Hyvä themes for Magento from scratch or migrate existing Luma shops cleanly, with Tailwind CSS, Alpine.js, and none of the unnecessary JavaScript baggage.

Luma-to-Hyvä Migration

Move an existing shop to Hyvä in a structured way, without losing functionality.

Custom Theme Development

Build a custom Hyvä theme from scratch based on your design.

Performance Optimization

Improve Core Web Vitals and load times in the Hyvä frontend with purpose.

10. Summary

GraphQL Fragments in Hyvä

Core problem

Duplicated product fields across many query files cause inconsistencies and high maintenance cost.

Solution

Layered, centrally organized fragments with a clear, feature-prefixed naming convention.

Caching effect

Consistent field shape from fragments enables lossless merging of product data in the client cache.

Upgrade safety

Versioned fragments and schema validation in the CI pipeline catch breaking changes before deployment.

11. FAQ: GraphQL Fragments in Hyvä

1What is the main advantage of GraphQL fragments over spelling out fields individually?
A fragment defines a field selection exactly once and can be reused across as many queries as needed. If a field changes, the change only needs to be maintained in that one place instead of being applied separately in every affected query file.
2Does Magento's GraphQL schema support fragments without extra configuration?
Yes, fragments are part of the GraphQL specification and work with any spec-compliant server, including Magento, without needing a module or extension. Composition happens purely at the query-document level, before the request is ever sent.
3How many layers should a fragment structure have at minimum?
For most Hyvä themes, two to three layers are enough, for example a price fragment, a card fragment built on top of it, and a detail fragment for the product detail page. More layers increase reusability but eventually make the structure harder to follow.
4Where should reusable fragments live inside the theme?
A dedicated directory, for example under web/js/graphql/fragments, from which every query imports exactly what it needs, has proven effective. That way it stays obvious at a glance who actually uses a given fragment.
5How do you prevent naming collisions between fragments from different teams?
A feature-prefixed naming convention, such as PlpProductCardFields instead of a plain ProductCardFields, makes collisions unlikely from the start when the final query gets assembled. This convention should be agreed on early in the project and documented for the team.
6Do fragments actually improve load time noticeably?
The biggest effect is not compression, it is that every query contains only the fields it actually needs instead of accidentally dragging along excess fields from an oversized fragment. That measurably reduces both resolver work and response size.
7How do fragments help with client-side caching in the Hyvä frontend?
Because the same entity comes back with the same field shape across different views, cache entries for the same SKU can be merged losslessly. Without consistent fragments, several slightly different partial slices of the same entity end up in the cache instead.
8What happens when Magento deprecates a field in the GraphQL schema?
If all affected fields live in central fragments, the fix can be applied in one single place instead of having to search for it in every individual query file. For larger changes, a parallel, versioned fragment variant during the migration phase is worth introducing.
9Is automated schema validation for fragments in the CI pipeline worth it?
Yes, a script that checks fragment field selections against an introspected schema catches breaking changes right in the pull request. Without that check, such changes often only surface live once customers report broken product cards.
10At what point does fragment composition become worthwhile in a Hyvä project?
As soon as more than two or three views query the same product fields, for example the category page, the cart, and the wishlist, the maintenance benefit already clearly outweighs the initial restructuring effort. For very small, one-off queries, the extra layer of indirection rarely pays off.