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.

GraphQL
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).

The field belongs to an entity
Next, determine the source of the fields it depends on. Do they live in a different subgraph or this subgraph?
GraphQL
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}
The fields it depends on live in a different subgraph
In this subgraph, use @requires on the computed field and mark the depended-on fields @external so the router can fetch them before your resolver runs.
GraphQL
1type Product @key(fields: "id") {
2  id: ID!
3  size: Int @external
4  weight: Int @external
5  shippingEstimate: Int @requires(fields: "size weight")
6}
The fields it depends on live in this subgraph
When all the data lives in the same subgraph, resolve the field in its resolver function. You don't need any federation directives.
GraphQL
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}
TypeScript
resolvers.ts
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};
The field doesn't belong to an entity
GraphQL
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}
If the field doesn't belong to an entity, determine if the type can be uniquely identified by one or more fields.
The type can be uniquely identified by one or more fields.
Add a @key to the type so it becomes an entity. Then follow the steps above for a field that belongs to an entity.
GraphQL
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}
The type cannot be uniquely identified by one or more fields
If the type can't be uniquely identified by one or more fields, determine if the data you need lives on a parent entity.In this example, the Price.discountedTotal field needs the price and quantity fields from the OrderLine type. They share a parent entity: Order.
GraphQL
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}
The data you need lives on a parent entity
Move the computed field to that parent type so the router can resolve it.
GraphQL
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}
TypeScript
resolvers.ts
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};
The data lives only in another subgraph, with no shared parent entity
Restructure your schema. Merge the data into one subgraph, or introduce an entity both sides can reference.
The field is a root field on Query or Mutation, not an object type
GraphQL
1type Query {
2  product(id: ID!): Product!
3  # We want to compute shippingEstimate based on the product above
4  productShippingEstimate(id: ID!): Int!
5}
You don't need any federated directives. Resolve the data inside your subgraph using your data sources directly.

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.

GraphQL
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.

GraphQL
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}
TypeScript
resolvers.ts
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.

GraphQL
Products subgraph (existing owner)
1type Product @key(fields: "id") {
2  id: ID!
3  inStock: Boolean!
4}
GraphQL
Inventory subgraph (new owner)
1type Product @key(fields: "id") {
2  id: ID!
3  inStock: Boolean!
4    @override(from: "Products")
5}

Use the label argument for a gradual rollout.

Feedback

Ask Community