Hygraph GraphQL Content Federation

This topic, within Platform Integration Deep Dives, covers integrating Hygraph, a hosted headless CMS whose content API is GraphQL from the ground up and which can federate data from other APIs into the same graph. It explains how content models become a GraphQL schema, how content stages separate drafts from published content, how tokens and permissions are scoped, how localization and remote sources work, and how webhooks and caching keep a frontend fresh.

Hygraph generates a GraphQL schema from the content models you define: each model becomes a type with queries for single entries, lists and connections, filters for every field, and mutations for writing. Frontends therefore work with typed queries against a schema that changes whenever the model does. Two features set Hygraph apart from other GraphQL CMSs. Content stages, most commonly DRAFT and PUBLISHED, are part of every query, so preview and production read the same schema with a different stage argument. And remote sources let models include fields resolved from external REST or GraphQL APIs, so a product entry can carry live stock data from a commerce system in the same query.

Hygraph's parts in a headless integrationEditors work in the Hygraph app on content models; Hygraph exposes a GraphQL Content API with content stages, served through its cached endpoint; remote sources resolve fields from external APIs during queries; the frontend queries PUBLISHED for readers and DRAFT for preview with scoped tokens; signed webhooks on publish trigger revalidation.Hygraph appmodels, entriesContent APIGraphQL, stagesRemote sourcesREST, GraphQLWebhookssignedFrontendtyped queriesReadersremote fieldsPUBLISHED / DRAFTrevalidate
One GraphQL endpoint serves both stages and federated fields.

Integration Contract

A Hygraph integration rests on a few decisions. Endpoint: each project and environment has its own Content API endpoint, including the region; the high-performance endpoint serves cached reads and suits frontends. Stages: readers get stage: PUBLISHED, previews get stage: DRAFT, set explicitly on queries or through a token’s default stage. Tokens: permanent auth tokens carry content permissions per model, stage and locale; a read-only token for published content, a separate token that can read drafts for preview, and no mutation rights for either. Public API permissions can allow unauthenticated reads of published content if the site does not need a token at all. Environments: the master environment serves production; development environments hold schema changes until they are applied. Events: webhooks with a secret, triggered on publish, unpublish and delete, call one revalidation handler.

Bash
# .env: Hygraph integration
HYGRAPH_ENDPOINT=https://eu-central-1.cdn.hygraph.com/content/<project-id>/master
HYGRAPH_READ_TOKEN=pat_published_read_only       # server only; PUBLISHED stage
HYGRAPH_PREVIEW_TOKEN=pat_draft_read_only        # server only; DRAFT stage
HYGRAPH_WEBHOOK_SECRET=secret_set_on_the_webhook
HYGRAPH_MANAGEMENT_TOKEN=token_for_schema_migrations_in_ci   # never deployed with the frontend

Core Implementation Pattern

A small GraphQL client with a stage-aware fetch helper covers most integrations. Plain fetch is enough; heavier clients are only worth it when the browser needs a normalized cache.

TypeScript
// lib/hygraph.ts
import { draftMode } from "next/headers";

type Vars = Record<string, unknown>;

export async function hygraph<T>(query: string, variables: Vars = {}, tags: string[] = []): Promise<T> {
  const draft = (await draftMode()).isEnabled;
  const res = await fetch(process.env.HYGRAPH_ENDPOINT!, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: `Bearer ${draft ? process.env.HYGRAPH_PREVIEW_TOKEN : process.env.HYGRAPH_READ_TOKEN}`,
    },
    body: JSON.stringify({ query, variables: { ...variables, stage: draft ? "DRAFT" : "PUBLISHED" } }),
    ...(draft ? { cache: "no-store" as const } : { next: { tags } }),
  });
  const json = await res.json();
  if (!res.ok || json.errors) throw new Error(`Hygraph: ${json.errors?.[0]?.message ?? res.status}`);
  return json.data as T;
}
TypeScript
// queries/article.ts
export const ARTICLE = /* GraphQL */ `
  query Article($slug: String!, $stage: Stage!, $locales: [Locale!]!) {
    article(where: { slug: $slug }, stage: $stage, locales: $locales) {
      id
      title
      excerpt
      content { json references { ... on Asset { id url width height } } }
      author { name slug }
      coverImage { url(transformation: { image: { resize: { width: 1200 } } }) width height altText }
    }
  }
`;

Every query takes $stage as a variable, so one query serves preview and production. Locales are passed as an ordered list, which doubles as the fallback chain: [de_DE, en] returns German where it exists and English otherwise. The rich text field returns a JSON document plus referenced entries and assets, rendered with Hygraph’s rich text renderer and a component map for embeds.

Why this shape? A single fetch helper guarantees that navigation, footers and metadata respect draft mode just like page bodies. Passing stage and locales as variables keeps queries static, which makes them easy to type, persist and cache. And keeping tokens on the server avoids exposing even read-only credentials that may grant more than intended, such as access to models not meant for the public.

Caching & Invalidation Strategy

Hygraph’s high-performance endpoint caches query results at the edge and invalidates them when content changes, so reads are fast and fresh without extra work. The frontend’s own caches still need invalidation. Tag cached fetches by model and entry, such as article:{id} and article:list, and revalidate them from webhooks when entries are published, unpublished or deleted.

Hygraph signs webhooks when a secret key is configured: the gcms-signature header contains a signature, the environment name and a timestamp, and Hygraph’s utilities package provides a function to verify it against the raw body. Reject requests that fail verification or are older than a few minutes. The details are in verifying Hygraph webhook signatures.

For static sites, a webhook triggers a rebuild, debounced so a release that publishes dozens of entries becomes one build. Queries sent as GET requests with persisted query ids can also be cached by your own CDN, as described in making GraphQL responses CDN-cacheable. Remote fields have their own caching behaviour, depending on the remote API’s responses; treat them as the slowest part of any query.

Publish to fresh pageAn editor publishes an entry; Hygraph invalidates its own cached query results and sends a signed webhook; the handler verifies the signature and timestamp and revalidates tags; the next render queries the PUBLISHED stage and receives fresh content from the endpoint.HygraphWebhook handlerFrontend renderpublishinvalidate cached resultsentry published (gcms-signature)verifyrevalidateTag article:idquery stage PUBLISHEDfresh entry
Hygraph refreshes its edge cache itself; the webhook refreshes the frontend's.

Schema & Content Modeling Considerations

Every field in a Hygraph model becomes part of the GraphQL schema, so modeling decisions are API decisions. Field API ids become GraphQL field names; choose them as carefully as function names, since renaming one breaks every query that uses it. Relations are two-sided by default, so an article’s author field also adds an articles field to the author type, which is convenient for queries and costly if both sides are traversed carelessly.

Components and modular component fields express page-builder layouts: a sections field accepting several component types returns a union that queries handle with inline fragments. Each component type in the union adds a fragment to page queries, and the query size grows with the number of allowed components; keep the set deliberate, as described in modeling page-builder blocks with discriminated unions.

Localization is field-level: fields marked localizable store a value per locale, and non-localized fields are shared. Queries pass locales in order of preference and receive the first available value per entry. The localizations field returns an entry’s other locales, useful for hreflang and language switchers. Keep slugs localized when URLs are translated, and filter queries by the localized slug with the locale set.

Query complexity grows with nesting and list sizes. Hygraph limits complexity and page sizes, so deep queries over large lists fail rather than return partial data. Paginate lists with first and skip or with connection queries, as covered in paginating large collections.

Preview & Draft Workflow

Hygraph’s DRAFT stage holds the latest saved version of every entry; PUBLISHED holds what readers see. Preview therefore needs no separate API: the frontend enables its draft mode, switches the stage variable to DRAFT and uses the preview token. In the Hygraph app, preview URLs configured per model open the frontend’s draft route with the entry’s slug, so editors click one button to see their changes. The general patterns are in preview and draft workflow patterns, and the Hygraph specifics in previewing Hygraph drafts with content stages.

Keep three rules. The draft route validates a secret before enabling draft mode. The preview token can read drafts but not write, and never reaches the browser. And draft responses carry noindex and bypass shared caches. For scheduled releases, Hygraph can publish sets of entries at a given time; previewing a release means querying DRAFT for the release’s entries before the scheduled time.

Error Handling & Resilience

GraphQL responses can contain errors alongside partial data, with an HTTP status of 200. Treat any errors entry as a failure for page rendering unless the query was designed for partial results, and log the error messages with the query name. Rate-limited requests return 429; retry with backoff and moderate concurrency during builds. Queries that exceed complexity limits fail with a clear error; split them rather than retrying.

Remote fields add a new failure mode: the remote API may be slow or unavailable. Hygraph returns an error for the remote field, and the rest of the entry may still be present. Decide per page whether a missing remote value is acceptable, such as hiding stock information, or fatal, and code the fallback explicitly. For the frontend as a whole, serve the last good cached page when Hygraph is unreachable, and alert on sustained error rates rather than single failures.

Testing & Observability

Test queries against a staging environment with fixture content, asserting the shape of the response for each page type, and run them in CI whenever queries or the schema change. Generated types from the schema, with a drift check in CI, catch renamed fields and changed types before deployment; see automated testing for headless integrations for the broader approach.

Log every query with its operation name, stage, duration and whether it returned errors, and track remote field errors separately. Dashboards for query duration by operation show which pages are expensive, often those with deep relations or remote fields. Webhook logs, with the model, entry id and tags revalidated, make freshness problems traceable.

TypeScript
// lib/hygraph-log.ts
export function logQuery(e: { op: string; stage: "DRAFT" | "PUBLISHED"; ms: number; errors: number; remoteErrors: number }) {
  console.log(JSON.stringify({ kind: "hygraph_query", ...e }));
}

Content Federation with Remote Sources

Remote sources connect external APIs to the Hygraph schema. A REST or GraphQL remote source is configured once with its base URL and headers, and remote fields on models call it with values from the entry, such as a product’s SKU. The frontend then queries product content and live commerce data in one request, and editors see the remote data next to the content in the app. This is convenient and reduces frontend code, but it couples page rendering to the remote API’s latency and availability. Use remote fields for data that changes often and is cheap to fetch, cache aggressively at the frontend, and avoid them in list queries that would call the remote API once per entry. The patterns are covered in federating remote data with remote sources, and the architecture-level view in federating multiple headless CMS sources with GraphQL.

Worked Example

A consumer electronics brand used Hygraph for product marketing content and a commerce platform for prices and stock. With remote sources, product pages queried both in one request; with content stages, previews used the same queries with the DRAFT stage; with signed webhooks, publishes revalidated product and category tags. The team removed a custom aggregation service that had merged CMS and commerce data, which had been the most frequent cause of incidents. Page data code shrank noticeably, and incidents related to data aggregation stopped after the service was retired.

Data-related incidents per quarterProduction incidents caused by content and commerce data aggregation, per quarter, with a custom aggregation service and with Hygraph remote sources.Custom aggregation service5 incidents per quarterHygraph remote sources1 incidents per quarter
Moving aggregation into the content graph removed the service that failed most often.

Environments and Schema Changes

Hygraph projects have a master environment and optional development environments cloned from it. Make schema changes in a development environment, test them with a preview deployment pointed at that environment’s endpoint, then apply them to master with the management tooling or by repeating the change, following an expand-and-contract approach so the frontend never queries a field that does not exist yet. Scripted schema migrations with the management SDK make changes repeatable and reviewable, which matters once several developers change models. Keep content changes and schema changes apart: content moves between environments only when cloning, while schema moves through reviewed migrations.

Permissions and Tokens

Permanent auth tokens are scoped by permissions: which models, which stages, which locales and which actions. Create one token per purpose and environment: a published read token for the site, a draft read token for preview, and separate tokens for any integration that writes content, such as an import job. Grant each only the models it needs. Public API permissions, which allow reads without a token, are convenient for fully public sites but apply to every client on the internet; restrict them to the PUBLISHED stage and to models that are genuinely public, or leave them off and use a server-side token.

Assets and Images

Hygraph stores uploaded assets and serves them through its asset CDN, with image transformations requested through the url field’s transformation argument or through URL parameters. Request the width the layout needs, a modern format and a sensible quality, and build srcset values from a small set of widths so browsers pick the right size. Query width, height and alt text together with the URL, so images reserve their space and remain accessible. Transformed URLs are stable for the same parameters, which lets every cache layer keep them for a long time. For large media libraries, keep transformation parameters in one helper, since scattered ad-hoc sizes multiply the variants the CDN has to produce and cache.

Querying Efficiently

Because every field and relation is queryable, it is easy to write queries that fetch far more than a page shows. Write one query per page type, select only rendered fields, and use fragments for shared shapes such as teasers and SEO metadata, so changes happen in one place. Avoid traversing two-sided relations in both directions in one query, and paginate lists instead of fetching everything. Name every operation: named queries make logs, performance dashboards and error reports readable, and they are required for persisted queries. Review the slowest operations regularly; they usually point to a nested relation or a remote field that can be moved to a separate, cached request.

Ownership

Developers own queries, generated types, the fetch helper, webhook handler and schema migrations. Content designers own models and their fields together with developers, since every field is part of the API. Editors own entries, releases and publishing. Remote sources have an extra owner: the team behind the remote API, who must know that page rendering now depends on their latency and availability. Agree on timeouts and an escalation path with them before going live.

Frequently Asked Questions

Do I need a GraphQL client library?

No. A typed fetch helper covers server-rendered and static sites. Client libraries with normalized caches help in highly interactive browser applications.

Can the read token be public?

Avoid it. Use public API permissions restricted to published content if the browser must query Hygraph directly; otherwise keep tokens on the server.

How do locale fallbacks work?

Pass locales in order of preference. Hygraph returns the first locale with content for each entry; an entry with no content in any listed locale is not returned.

Are remote fields cached?

The frontend should cache pages containing them like any other data. Remote API latency adds to query time on cache misses, so keep remote calls light.

What happens to queries when a field is renamed?

They fail with a GraphQL error. Add the new field first, migrate queries, then remove the old field once no deployed frontend still queries it.

Can Hygraph serve several sites?

Yes, with a site field or separate models per site in one project, or with separate projects when sites need different schemas, permissions or release schedules.