GraphQL Federation skill for Codex

One owner per field across every subgraph. Codex maps who owns each field in your supergraph, then adds entities and subgraphs with the right Federation 2 directives, batched __resolveReference lookups and nullable cross-subgraph fields. It composes locally with Rover, reads the query plan for extra hops, runs subgraph checks before publishing, and moves field ownership with @override instead of risky big-bang cutovers.

  • Backend & APIs
  • $graphql-federation
  • 235 lines
  • 2 files
  • ~2,100 tokens
  • MIT
  • Updated

Install

curl -fsSL codexskills.dev/install.sh \  | sh -s -- graphql-federation

Installs to ~/.agents/skills/graphql-federation/ for every project on this machine.

104-line POSIX script. No sudo, no telemetry. Read install.sh

Use it in Codex

Start a prompt with $graphql-federation, or pick it from /skills. You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.

What Codex reads before loading it

Builds and evolves federated GraphQL supergraphs with Apollo Federation 2 - subgraph boundaries, entities with @key and reference resolvers, @shareable/@requires/@override, composition with Rover, schema checks in CI and router configuration. Use when splitting a monolithic GraphQL API across teams, adding a subgraph or entity, moving field ownership between services, debugging composition errors or slow query plans, or wiring rover subgraph check into a pipeline.

Use it when

  • Adding a subgraph or entity to a federated GraphQL API
  • Composition or rover subgraph check is failing
  • Moving field ownership between teams or services
  • Query plans through the router are slow or fan out too much

Use something else when

How to check the result

rover supergraph compose and rover subgraph check pass, and a router query touching each changed subgraph returns data with the expected fetch count in its query plan.

The mistake it prevents

Making fields contributed by another subgraph non-null, so one subgraph outage fails every query that touches the entity.

~/.agents/skills/graphql-federation/SKILL.md

--- name: graphql-federation description: Builds and evolves federated GraphQL supergraphs with Apollo Federation 2 - subgraph boundaries, entities with @key and reference resolvers,… license: MIT ---

A healthy supergraph has one owner per field, entities keyed by stable IDs, subgraphs that never call each other to answer a query, and composition plus breaking-change checks that run before any subgraph deploys.

Workflow

- [ ] 1. Map the current supergraph and ownership
- [ ] 2. Decide boundaries and entities
- [ ] 3. Write subgraph SDL with federation directives
- [ ] 4. Implement reference resolvers with batching
- [ ] 5. Compose locally and inspect query plans
- [ ] 6. Check, publish, then deploy

1. Map what exists

  • Read AGENTS.md, then locate each subgraph’s schema and the composition config (supergraph.yaml, GraphOS graph ref, Cosmo/Hive config).
  • Note the federation version in each subgraph’s @link and the router version. All directives you use must exist in the version the router and composition support.
  • List entities (@key types) and which subgraph contributes which fields. A table of type, field, owner is the fastest way to spot accidental shared ownership.

2. Boundaries and entities

SignalDecision
A team owns the data and its lifecycleThat team’s subgraph owns the type’s core fields
Another domain needs to attach fields to the typeIt contributes fields to the same entity via @key
A value object (Money, Address) appears in many subgraphsDefine it identically and mark @shareable
A subgraph only returns references to a type it cannot load@key(fields: "id", resolvable: false) stub
Field needs data owned elsewhere to compute@requires with @external fields
Two subgraphs would need a synchronous call to each otherBoundary is wrong; move the field or the data

Keys: use the narrowest stable identifier (id), not mutable fields like email. Compound keys (@key(fields: "sku region")) are fine when the identity really is compound.

3. Subgraph SDL

# products subgraph
extend schema
  @link(url: "https://specs.apollo.dev/federation/v2.7",
        import: ["@key", "@shareable", "@inaccessible"])

type Product @key(fields: "id") {
  id: ID!
  name: String!
  price: Money!
  weightGrams: Int
}

type Money @shareable {
  amount: Int!
  currency: String!
}

type Query {
  product(id: ID!): Product
}
# shipping subgraph
extend schema
  @link(url: "https://specs.apollo.dev/federation/v2.7",
        import: ["@key", "@external", "@requires"])

type Product @key(fields: "id") {
  id: ID!
  weightGrams: Int @external
  shippingEstimate: ShippingEstimate @requires(fields: "weightGrams")
}
  • In Federation 2, contributing fields to an entity needs no extend type.
  • Fields contributed by another subgraph should usually be nullable: if that subgraph is down, the router can null just that field instead of failing the parent.
  • Hide internal fields from the public API with @inaccessible rather than removing them.

4. Reference resolvers

Every resolvable entity needs a reference resolver. The router sends batches of representations to _entities, so batch the lookup.

import { buildSubgraphSchema } from '@apollo/subgraph';
import DataLoader from 'dataloader';

const resolvers = {
  Product: {
    __resolveReference(ref: { id: string }, ctx: Ctx) {
      return ctx.loaders.productById.load(ref.id);
    },
  },
};

export const schema = buildSubgraphSchema({ typeDefs, resolvers });

// per request:
const loaders = {
  productById: new DataLoader(async (ids: readonly string[]) => {
    const rows = await db.product.findMany({ where: { id: { in: [...ids] } } });
    const byId = new Map(rows.map((r) => [r.id, r]));
    return ids.map((id) => byId.get(id) ?? null);
  }),
};

Other stacks follow the same pattern: gqlgen generates Find<Entity>By<Key> resolvers, graphql-ruby uses resolve_reference, Strawberry uses resolve_reference classmethods, Hot Chocolate uses [ReferenceResolver]. Always return null (not throw) for missing IDs.

5. Compose locally

# supergraph.yaml
federation_version: =2.7.1
subgraphs:
  products:
    routing_url: http://localhost:4001/graphql
    schema:
      file: ./products/schema.graphql
  shipping:
    routing_url: http://localhost:4002/graphql
    schema:
      file: ./shipping/schema.graphql
rover supergraph compose --config ./supergraph.yaml > supergraph.graphql

Rover downloads the composition plugin on first run, which needs network; in a sandbox without network, ask before running or use an already-installed plugin. rover dev runs a local router against running subgraphs for end-to-end testing.

Inspect the query plan for every new cross-subgraph query (Apollo Sandbox or the router’s query-plan exposure in dev). Each extra fetch node is a network hop; sequential fetches chained through @requires add latency.

Common composition errors:

Error themeFix
Field defined in multiple subgraphs but not shareableMark both definitions @shareable, or move the field to one owner
Type definitions differ between subgraphsMake shared value types identical (fields, nullability)
Field cannot be satisfied from this entry pointAdd a @key in the subgraph that needs to jump, or move the field
Directive not importedAdd it to the @link import list

6. Check, publish, deploy

rover subgraph check my-graph@prod --name products --schema ./products/schema.graphql
rover subgraph publish my-graph@prod --name products \
  --schema ./products/schema.graphql --routing-url https://products.internal/graphql
  • Run check on every pull request; it validates composition and compares against real operation usage. Treat failures as blocking.
  • Order of operations for additive changes: deploy the subgraph code, then publish the schema. For removals: publish the schema without the field (after usage drops to zero), then remove the code.
  • Publishing and production router changes affect live traffic; confirm with the user before running publish against a production graph ref.

Moving a field between subgraphs

  1. Implement the field in the new subgraph with @override(from: "old-subgraph").
  2. Deploy the new subgraph code, then publish; the router now routes the field to it.
  3. Watch error rates and latency; roll back by removing @override and republishing.
  4. Remove the field from the old subgraph, then remove @override.

Progressive override (@override(from: "products", label: "percent(10)"), Federation 2.7+) shifts traffic gradually; confirm your router and plan support it before relying on it.

Router essentials

# router.yaml
headers:
  all:
    request:
      - propagate:
          named: authorization
include_subgraph_errors:
  all: false
  • Subgraphs must not be reachable from the internet; only the router should call them.
  • Propagate only the headers subgraphs need; never forward cookies wholesale.
  • Set per-subgraph timeouts and enable query depth/cost limits at the router.

Gotchas

  • A subgraph calling another subgraph’s HTTP API to resolve a field reintroduces the distributed monolith federation was meant to remove.
  • Non-null contributed fields turn one subgraph’s outage into errors on every query that touches the entity.
  • Forgetting batching in __resolveReference produces one database query per entity in a list: classic N+1 across the network.
  • @shareable on fields with different resolver logic in two subgraphs gives clients inconsistent answers depending on the query plan.
  • Federation 1 subgraphs (no @link) compose under different rules; migrate them to Federation 2 syntax before adding new directives.

Verification

  • rover supergraph compose succeeds locally with no hints you have not reviewed.
  • rover subgraph check passes against the target variant.
  • An end-to-end query through the router returns data from each touched subgraph, and the query plan shows the expected number of fetches.
  • Kill one contributing subgraph locally and confirm the parent query still returns partial data with an error, not a total failure.

Output template

## Federation change
- Subgraphs touched: <names>
- Entities and keys: <Type @key(fields)>
- Field ownership changes: <field: old -> new, mechanism>
- Composition: <pass, hints reviewed>
- Check result: <link or summary>
- Deploy order: <steps>

What the zip contains

SKILL.md plus the interface file below. Nothing runs on install.

agents/openai.yamlgenerated from this page

Gives the Codex app a display name, a one-line description and a starter prompt for this skill.

interface:
  display_name: "GraphQL Federation"
  short_description: "One owner per field across every subgraph"
  default_prompt: "$graphql-federation Split the reviews part of our GraphQL API into its own subgraph"

About this skill

Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.

esc
Just launched

Codex Skills Pro is live

79 premium skills for Codex, each with scripts, references and an evidence report that proves the result.

Bought one by one$882.21
All 79 skills$49
94% off

That's $0.62 a skill, paid once.

Get all 79 for $49Or pick single skills from $4.99 →

Secure checkout by Creem