skip to content

How does @override(from:) move field ownership between subgraphs safely?

level: seniorimportance: should knowfreq 40%

answer

  1. Two declarations, briefly, on purpose
  2. The new subgraph makes the claim
  3. Name the subgraph you are taking it from
  4. Publish first, delete the old one later
  5. The end state is a removed declaration

basics

~20 s

The receiving subgraph declares the field with @override(from:) naming the subgraph it is taking over from; composition then routes every request for that field to the new one. Deploy the new implementation first, compose, verify, then delete the old field.

solid answer

~50 s

`@override(from: "Lockers")` on a field in the receiving subgraph tells composition that this subgraph now resolves it and the named one no longer does. Both declarations may coexist — that is the point, since without the directive the duplicate would be rejected — which makes the migration a sequence rather than a flag day: ship the new subgraph with the implementation and the directive, compose and publish the supergraph, watch traffic move, then remove the field from the old subgraph in a later, separate change. The `from` value is the subgraph's registered name in the composition, not a URL or a repository, so a typo means the takeover simply does not happen. Newer Federation versions add a `label` argument for progressive override, letting a percentage of traffic route to the new implementation so you can ramp instead of switching at once.

code

graphql · 11 lines
graphql
# Lockers subgraph — unchanged during the migration
type Locker @key(fields: "id") {
  id: ID!
  activeHolds: Int!
}

# Reservations subgraph — takes over resolution of the field
type Locker @key(fields: "id") {
  id: ID!
  activeHolds: Int! @override(from: "Lockers")
}

go deeper

for a junior

Know what the directive is for: it lets one subgraph take over resolving a field another subgraph currently resolves, without composition rejecting the temporary duplicate.

for a middle

Explain the mechanics — what the from argument names, why both declarations may coexist, and what composition records in the supergraph as a result.

for a senior

Own the rollout order and the rollback: implement, compose and publish, verify, then delete later; and be able to say why routers adopting the supergraph at different times forces that ordering.

for a principal

Treat it as a handover, not an edit. Decide when a field should change teams at all, whether a ramp is warranted for the latency risk, and how you ensure the final cleanup is scheduled rather than forgotten.

## What the directive changes Ownership of a field is normally implied: whichever subgraph declares it resolves it, and a second declaration is a composition failure. That rule makes moving a field between services awkward, because the two states you want — old subgraph resolves it, new subgraph resolves it — are separated by a moment where both declare it. `@override` legalises that moment and gives it a direction: ```graphql # Reservations subgraph — taking over the field type Locker @key(fields: "id") { id: ID! activeHolds: Int! @override(from: "Lockers") } ``` Composition reads this as: `Locker.activeHolds` is now resolved by Reservations; the declaration still present in the Lockers subgraph is superseded and will not be used. The supergraph records the new routing, and every router that picks up that supergraph plans the field to the new subgraph. The `from` argument is the subgraph's **registered name** in the composition — the name it was published under, not its URL, service name in an orchestrator, or repository. Get it wrong and you have written a directive that overrides nothing. ## Why it is a sequence, not a switch The safe migration has four steps, and the ordering is the answer an interviewer is listening for. 1. **Implement in the new subgraph, with the directive.** The receiving subgraph must be able to resolve the field for real — including any data access it did not previously need — before the supergraph is composed. Deploy it and let it run; nothing routes to the field yet. 2. **Compose and publish.** This is the moment traffic moves. It is a supergraph change, not a service deploy, so it is reversible by publishing the previous supergraph — which is only true while step 4 has not happened. 3. **Verify.** Compare per-field latency and error rate against the old implementation, and compare values if you can. The old subgraph is still capable of serving the field, which is exactly what makes a rollback cheap. 4. **Remove the field from the old subgraph, later.** Only after routers have all picked up the new supergraph and you are satisfied. Doing this in the same change as step 2 throws away the rollback and creates a window where a router still on the previous supergraph plans a field to a subgraph that no longer has it. That last point is the one people miss. Routers pick up a published supergraph asynchronously; during the rollout, old and new plans coexist. Both implementations must be live across that window. ## Ramping instead of switching Switching a field in one step is fine when the new implementation is cheap and well understood. It is uncomfortable when the field sits on a hot path with a tight budget — if a graph is holding a 340 ms p99 and the field's new implementation crosses a service boundary the old one did not, you would rather find that out on a slice of traffic than on all of it. Newer Federation versions support progressive override: a `label` argument alongside `from` lets the router send a share of requests to the new implementation and the rest to the old one, so you can ramp and watch. Check what your composition and router versions support before designing a plan around it, and remember that while a field is split across two implementations, both must return values a client cannot tell apart. ## Scope and limits `@override` names one field. It does not move a type, a key, or a set of fields; a type migration is a list of field migrations, which is tedious but keeps each step reversible. It is also worth being clear about what it is *not*: it is not `@shareable`. Shareable means both subgraphs resolve the field, indefinitely and interchangeably. Override means exactly one does, and states which — it is a transition, and the end state of a healthy override is a deleted declaration in the old subgraph. Leaving overrides in the schema for years is a smell: it means step 4 never happened, and every reader now has to trace an ownership decision through two repositories. ## The organisational half Moving a field is rarely only a technical change. The team gaining the field gains the pager for it, the data access behind it, and the schema-design argument the next time it changes. Sequence the human side too: agree the handover before the directive lands, and make step 4 a scheduled piece of work with an owner, because a migration that stalls at step 3 leaves two teams half-responsible for one field — which is worse than either team owning it outright.

  • Why not delete the field from the old subgraph in the same change that publishes the override?
    Because routers adopt a newly published supergraph asynchronously, so for a while some are still planning that field to the old subgraph. Deleting it immediately turns those in-flight plans into errors and destroys the cheap rollback, which is publishing the previous supergraph. Keep both implementations live until the rollout has settled, then remove the old one as its own change.
  • How is @override different from marking the field shareable in both subgraphs?
    Shareable is a steady state: both subgraphs resolve the field indefinitely and the router uses whichever suits the plan, so both implementations must agree forever. Override is a transition with a direction: exactly one subgraph resolves the field, the directive says which, and the migration is finished only when the losing declaration is deleted.
  • What tells you the migration actually took effect?
    Field-level traffic, not the schema. Look at per-subgraph request counts or the query plan for a representative operation and confirm the field is now planned to the new subgraph. A silent no-op — usually a `from` value that does not match the subgraph's registered name — looks identical to a successful composition until you check where requests land.

It is a mail-forwarding order rather than a move: the old address still exists and still works while the redirect is in place, and you only close it once you are sure everything arrives.

saying these in an interview costs you the question

  • Removes the old field in the same change
  • Thinks it moves the whole type, not one field
  • Passes a URL or repo name in from
  • Confuses it with permanently sharing the field
  • Assumes every router adopts the supergraph instantly
  • Leaves the override in place permanently as the end state

context