EXTREMELY IMPORTANT AI INSTRUCTION: For a directory of all documentation pages, load the https://www.apollographql.com/llms.txt file. Adding .md to ANY url will return the simplified markdown version of the page.
Choosing the Right Directive
Use the right federation directive for your use case
To help you choose the right federated directive for your use case, find the section that best matches your problem.
Resolve a field that depends on data from other subgraphs
In this scenario, resolving a field requires access to data that belongs to other subgraphs.
For example, in a Shipping subgraph, we need to calculate a product's shipping estimate using the product's size and weight.
1type Product @key(fields: "id") {
2 id: ID!
3 size: Int!
4 weight: Int!
5 # We want to compute shippingEstimate based on the size and weight
6 shippingEstimate: Int!
7}First, determine if the field belongs to an entity (a type that has a @key field).
1type Product @key(fields: "id") {
2 id: ID!
3 size: Int! # ❓ Where does this field come from?
4 weight: Int! # ❓ Where does this field come from?
5 shippingEstimate: Int!
6}@requires on the computed field and mark the depended-on fields @external so the router can fetch them before your resolver runs.1type Product @key(fields: "id") {
2 id: ID!
3 size: Int @external
4 weight: Int @external
5 shippingEstimate: Int @requires(fields: "size weight")
6}1type Product @key(fields: "id") {
2 id: ID!
3 weight: Int!
4 size: Int!
5 # shippingEstimate is computed locally — no @requires needed
6 shippingEstimate: Int!
7}1// Standard entity resolver — everything is resolved locally,
2// no router re-entry needed.
3const resolvers = {
4 Product: {
5 __resolveReference(ref: { id: string }) {
6 return fetchProductById(ref.id); // returns weight, size, etc.
7 },
8 shippingEstimate(product: { weight: number; size: number }) {
9 // All data is local — compute directly.
10 return calculateEstimate(product.weight, product.size);
11 },
12 },
13};1type Product {
2 id: ID!
3 size: Int!
4 weight: Int!
5 # We want to compute shippingEstimate based on the size and weight
6 shippingEstimate: Int!
7}@key to the type so it becomes an entity. Then follow the steps above for a field that belongs to an entity.1type Product @key(fields: "id") {
2id: ID!
3size: Int!
4weight: Int!
5# We want to compute shippingEstimate based on the size and weight
6shippingEstimate: Int!
7}Price.discountedTotal field needs the price and quantity fields from the OrderLine type. They share a parent entity: Order.1type Order @key(fields: "id") {
2 id: ID!
3 items: [OrderLine!]! @external
4 price: Price!
5}
6
7type OrderLine {
8 price: Float! @external
9 quantity: Int! @external
10}
11
12type Price {
13 # This is the field we want to compute and it needs
14 # the price and quantity fields from the OrderLine type
15 # OrderLine and Price types have a parent entity: Order
16 discountedTotal: Float
17}1type Order @key(fields: "id") {
2 id: ID!
3 discountedTotal: Float
4 @requires(fields: "items { price quantity }")
5 items: [OrderLine!]! @external
6}
7
8type OrderLine {
9 price: Float! @external
10 quantity: Int! @external
11}1// @requires on a nested selection — the router hydrates the full
2// items array (with price and quantity) before calling __resolveReference.
3const resolvers = {
4 Order: {
5 __resolveReference(
6 ref: { id: string; items: Array<{ price: number; quantity: number }> }
7 ) {
8 // items[] is populated by the router via @requires — no extra fetch.
9 return ref;
10 },
11 discountedTotal(
12 order: { items: Array<{ price: number; quantity: number }> }
13 ) {
14 const subtotal = order.items.reduce(
15 (sum, item) => sum + item.price * item.quantity, 0
16 );
17 return subtotal * 0.9;
18 },
19 },
20};1type Query {
2 product(id: ID!): Product!
3 # We want to compute shippingEstimate based on the product above
4 productShippingEstimate(id: ID!): Int!
5}Deprecate or remove a field safely
In this scenario, you want to deprecate or remove a field without breaking composition. Add the @deprecated directive to the field, providing a reason for deprecation.
Use field usage metrics to assess when it's safe to remove the field.
After clients have migrated to the new field, remove the deprecated field and its resolvers.
1type Product @key(fields: "id") {
2 id: ID!
3 name: String!
4 legacySku: String @deprecated(reason: "Use sku instead")
5}Hide a field
In this scenario, you want to hide a field so it's not exposed to clients. Doing that is particularly useful when you're rolling out a new field and need to hide it until all subgraphs define it.
First, add the @inaccessible directive to the field. Then, after all subgraphs have defined the field, remove the @inaccessible directive so clients can use the field.
1# Subgraph A (already updated)
2type Position @shareable {
3 x: Int!
4 y: Int!
5 z: Int! @inaccessible # hidden from clients until Subgraph B adds it
6}
7
8# Subgraph B (not yet updated)
9type Position @shareable {
10 x: Int!
11 y: Int!
12}1resolvers.ts
2// @inaccessible does NOT remove the field from resolvers — it only hides it
3// from the public API schema. The resolver must still exist and return a value;
4// the router won't expose z to clients until the directive is removed.
5
6// Subgraph A resolver (field exists, is @inaccessible)
7const resolvers = {
8 Position: {
9 // Position is a @shareable value type — no __resolveReference needed
10 // (value types are not entities; they're inlined at the query path).
11
12 // z is @inaccessible: implement the resolver so the supergraph is valid,
13 // but clients cannot query it yet.
14 z(position: { x: number; y: number; z: number }) {
15 return position.z;
16 },
17 },
18};
19
20// Once Subgraph B also adds z and removes @inaccessible, clients can query z.
21// No resolver change is needed — remove the @inaccessible directive from
22// the schema and redeploy.Migrate a field from one subgraph to another
In this scenario, you want to migrate a field from one subgraph to another.
In the new owner subgraph, add the field and apply @override(from: "SubgraphName") to that field. That way, the router sends requests for the field to the new owner subgraph.
1type Product @key(fields: "id") {
2 id: ID!
3 inStock: Boolean!
4}1type Product @key(fields: "id") {
2 id: ID!
3 inStock: Boolean!
4 @override(from: "Products")
5}Use the label argument for a gradual rollout.