Migrating Apollo Federation v1 to v2 Directives

Moving a subgraph from Apollo Federation v1 to v2 means trading implicit federation conventions for an explicit, imported directive set that composes more predictably and unlocks @shareable, @override, and progressive ownership migration. This guide walks through the directive changes one subgraph at a time, with a before/after SDL block and a semantics comparison table, as part of Migrating and Versioning Federated Schemas.

When to Use This Migration

  • Your subgraphs still rely on v1’s implicit federation directives (no @link import) and you want the stricter, more transparent composition model of v2.
  • You need v2-only capabilities — @shareable value types, @override for field-ownership migration, @interfaceObject, or contract variants via @tag.
  • You are consolidating on the Apollo Router, which composes v2 supergraphs natively and gives clearer composition diagnostics than the v1 gateway.

Prerequisites

What Actually Changes

Federation v1 treated certain directives as built in. Every subgraph implicitly understood @key, @external, @requires, @provides, and @extends, and types defined elsewhere were brought in with extend type. Composition in v1 was permissive: overlapping fields were silently allowed, which is exactly where v1 graphs accumulated hidden inconsistencies.

Federation v2 makes the directive set explicit and opt-in through an @link to the federation spec. You import only the directives you use, and the composer treats the schema as a v2 subgraph with stricter rules. The headline differences:

The @link import is mandatory. A v2 subgraph begins by linking the federation spec and importing its directives. Without this line, the composer treats the schema as v1.

extend schema
  @link(url: "https://specs.apollo.dev/federation/v2.9",
        import: ["@key", "@shareable", "@external", "@override"])

extend type becomes type with @key. In v1 a subgraph that contributed to an entity it did not originate used extend type User @key(...). In v2 you simply write type User @key(...) — the composer figures out contribution from the directives, not the extend keyword. extend is still legal SDL but is no longer the federation signal it was; relying on it is a v1 habit to drop.

Shared value types now require @shareable. This is the change that breaks the most v1 graphs on first compose. In v1, two subgraphs could both define a non-entity type like type Address { ... } and composition allowed it implicitly. In v2 any field defined in more than one subgraph must be marked @shareable in every subgraph that defines it, or composition fails. This is intentional: it forces overlapping definitions to be a deliberate decision. For deeper treatment of when to share versus own a type, see Using @shareable Directive for Overlapping Types.

@key gains resolvable. v2 adds @key(fields: "id", resolvable: false) to declare that a subgraph references an entity by key but cannot resolve it — it only holds a foreign key. In v1 every @key implied a resolver. resolvable: false lets a subgraph point at an entity without implementing __resolveReference, which removes a class of “missing reference resolver” composition surprises.

@external and @provides semantics tighten. In v2 a field is @external only when the subgraph genuinely does not own it but needs it for @requires/@provides or as part of a key. The composer now validates @provides/@requires field selections against the actual owning subgraph, so v1 graphs that quietly over-declared @external will get composition errors that name the offending field — a feature, not a regression.

Directive Semantics: v1 vs v2

Directive Federation v1 Federation v2
Spec import Implicit; no @link Required @link(url: "…/federation/v2.x", import: [...])
Entity contribution extend type T @key(...) type T @key(...) (no extend needed)
@key Resolver always implied Adds resolvable: false for reference-only keys
Shared value types Implicitly allowed across subgraphs Must be @shareable in every defining subgraph
@external Loosely validated Validated against the owning subgraph’s definition
@provides / @requires Field selections lightly checked Field selections strictly validated at compose time
Field ownership migration Not supported @override(from: "subgraph"), incl. progressive label
Composition tool rover supergraph compose (v1 build) rover supergraph compose with federation_version: =2.x

Before / After SDL

The same products subgraph, first in v1 style and then migrated to v2.

# BEFORE — Federation v1 (implicit directives, extend type, no @shareable)
type Product @key(fields: "id") {
  id: ID!
  name: String!
  dimensions: Dimensions          # also defined in the shipping subgraph
}

type Dimensions {                  # silently shared in v1
  width: Float!
  height: Float!
}

extend type User @key(fields: "id") {
  id: ID! @external
  recentlyViewed: [Product!]!
}
# AFTER — Federation v2 (explicit @link, type + @key, @shareable, validated @external)
extend schema
  @link(url: "https://specs.apollo.dev/federation/v2.9",
        import: ["@key", "@shareable", "@external"])

type Product @key(fields: "id") {
  id: ID!
  name: String!
  dimensions: Dimensions
}

type Dimensions @shareable {        # overlap with shipping is now explicit
  width: Float!
  height: Float!
}

type User @key(fields: "id") {      # no more `extend`
  id: ID! @external
  recentlyViewed: [Product!]!
}

The corresponding subgraph server is unchanged in shape — buildSubgraphSchema reads the v2 SDL directly:

import { buildSubgraphSchema } from '@apollo/subgraph';
import { gql } from 'graphql-tag';
import { readFileSync } from 'node:fs';

// typeDefs is the migrated v2 SDL above.
const typeDefs = gql(readFileSync('./products.graphql', 'utf8'));

const schema = buildSubgraphSchema({ typeDefs, resolvers });
// __resolveReference on Product is unchanged from v1.

Staged Rollout

Federation v1 and v2 subgraphs compose together, so you migrate one subgraph at a time without a flag day. The composer accepts a mix and upgrades the supergraph as you go.

  1. Bump composition to federation 2. Set the version in your compose config so the composer applies v2 rules to v2-linked subgraphs while still accepting v1 subgraphs.
# supergraph.yaml
federation_version: =2.9.0
subgraphs:
  products:
    routing_url: https://products.svc/graphql
    schema: { file: ./products.graphql }
  shipping:
    routing_url: https://shipping.svc/graphql
    schema: { file: ./shipping.graphql }
  1. Migrate one subgraph’s SDL. Add the @link, drop extend from entity contributions, and add @shareable to any value type that overlaps another subgraph. Leave the others on v1.

  2. Check before publishing. Run a composition check so you catch the @shareable and @external validation errors locally rather than in production.

rover subgraph check my-graph@prod --name products --schema ./products.graphql
  1. Publish and watch. Publish the migrated subgraph; the router hot-reloads the recomposed supergraph. Confirm error rates stay flat, then move to the next subgraph.
rover subgraph publish my-graph@prod \
  --name products \
  --schema ./products.graphql \
  --routing-url https://products.svc/graphql
  1. Repeat until all subgraphs are v2. Once every subgraph links the v2 spec, you can adopt v2-only features like @override and contract variants, covered in Migrating and Versioning Federated Schemas.

Verification

After migrating a subgraph, confirm three things. First, local composition succeeds:

rover supergraph compose --config ./supergraph.yaml > supergraph.graphql

Second, the composition check against production passes — this is what proves the migration introduced no breaking change for live operations:

rover subgraph check my-graph@prod --name products --schema ./products.graphql
# Expect: "No changes / compatible" and no composition errors.

Third, run a real query through the router for a shared type (e.g. Product.dimensions) and confirm the response is identical to the pre-migration shape. Because v2 only changes how composition validates the schema — not the wire shape of valid responses — clients should see no difference.

Common Mistakes & Gotchas

Forgetting @shareable on overlapping value types. The most common first error: composition fails with a message that a non-shareable field is defined in multiple subgraphs. Add @shareable to the type in every subgraph that defines it, not just one.

Leaving @external on a field the subgraph actually owns. v1 tolerated loose @external; v2 validates it. If a field is genuinely owned by this subgraph, remove @external — otherwise the composer rejects the resolver mapping.

Mixing federation spec versions across @link imports. Pin a single federation/v2.x URL across subgraphs (or rely on federation_version in compose). Inconsistent minor versions can pull in directives one subgraph doesn’t recognise.

Frequently Asked Questions

Can v1 and v2 subgraphs run in the same supergraph during migration?

Yes. The composer accepts a mix, which is exactly what makes incremental migration possible. A subgraph is v2 once it includes the @link to the federation v2 spec; everything without that line is still treated as v1. Migrate one at a time and republish after each.

Do I have to change my resolvers when migrating to v2?

Almost never. The directive changes are about SDL and composition semantics; __resolveReference and field resolvers keep the same shape. The exception is if you adopt new v2 features afterward — for example, a field you @override from another subgraph now needs a resolver in the new owner.

What breaks first when I compose a v1 subgraph as v2?

Shared value types without @shareable. v1 allowed two subgraphs to define the same non-entity type implicitly; v2 requires @shareable on every copy. The composition error names the duplicated field, so add the directive to each defining subgraph and re-check.