Using @shareable Directive for Overlapping Types in GraphQL Federation
When two subgraphs both define the same field on the same type, Apollo Federation v2 refuses to compose the supergraph unless you declare the overlap intentional with @shareable — and getting that declaration exactly right is what this guide walks through.
This is a focused, hands-on companion to Type Ownership and Shared Schema Contracts, which frames the broader ownership model; here we stay on one mechanism within GraphQL Federation Architecture & Design: making an overlapping field compose cleanly and route predictably.
When to Use This Pattern
- Read-mostly fields several domains already hold. A
User.displayNameorProduct.skuthat two services can both produce identically, where letting the router pick either reduces network hops. - Cross-cutting attributes, not core ownership. Use it for genuinely duplicated, low-volatility fields — never to paper over the fact that you have not decided who owns a type.
- You have verified the signatures match. If the two subgraphs would resolve the field with different types, arguments, or nullability,
@shareableis the wrong tool; consolidate ownership or use@overrideinstead.
Prerequisites
Why Overlapping Types Fail Composition
Federation v2 enforces single-ownership by default. When composition finds a field defined in more than one subgraph and at least one of them has not marked it @shareable, it cannot decide which resolver is authoritative, so it aborts the build rather than risk non-deterministic routing at runtime. The error is explicit about both the offending field and the subgraphs involved:
INVALID_FIELD_SHARING: Non-shareable field "User.email" is resolved
from multiple subgraphs: it is resolved from subgraphs "auth" and
"commerce" and defined as non-shareable in at least one of them.
@shareable is the opt-in that tells composition the duplication is deliberate. Note that Federation v2 dropped the v1 extend type ownership model in favour of this flat, directive-explicit one: a subgraph contributing a field it does not own marks the key as @external; a subgraph independently resolving a duplicated field marks that field @shareable.
Implementation Walkthrough
Apply @shareable to the field in every subgraph that resolves it. Omitting it from even one participant reproduces the composition error above. The directive can sit at the field level (preferred, surgical) or the type level (all fields shareable — use sparingly).
Below, both auth and commerce resolve User.email. auth owns the entity; commerce references the key as @external and adds orderHistory, while also resolving email itself.
# auth subgraph — owns User
extend schema
@link(url: "https://specs.apollo.dev/federation/v2.9",
import: ["@key", "@shareable"])
type User @key(fields: "id") {
id: ID!
email: String! @shareable # deliberately resolvable here AND in commerce
name: String!
}
# commerce subgraph — contributes orderHistory, also resolves email
extend schema
@link(url: "https://specs.apollo.dev/federation/v2.9",
import: ["@key", "@external", "@shareable"])
type User @key(fields: "id") {
id: ID! @external # identity referenced, not owned
email: String! @shareable # SAME signature as auth: String!, no args
orderHistory: [Order!]! # owned solely by commerce
}
// commerce subgraph resolvers — note __resolveReference hydrates the entity
import { buildSubgraphSchema } from '@apollo/subgraph';
const resolvers = {
User: {
// Router supplies the key; we return the entity this subgraph can resolve.
__resolveReference(ref: { id: string }, ctx: Context) {
return ctx.dataSources.users.byId(ref.id);
},
// Both subgraphs must produce the same email for a given user.
email(user: { id: string }, _args: unknown, ctx: Context) {
return ctx.dataSources.users.emailFor(user.id);
},
},
};
export const schema = buildSubgraphSchema({ typeDefs, resolvers });
The signatures must match exactly across subgraphs — same scalar, same nullability, same arguments and defaults. @shareable authorises duplication; it never relaxes type compatibility. A String! in one subgraph and String in the other still fails with FIELD_TYPE_MISMATCH.
Field-level versus type-level @shareable
You can apply the directive to a whole type instead of individual fields. Type-level @shareable marks every field on the type as resolvable from multiple subgraphs, which is convenient for value types — small, owned-by-nobody structures like an Address or Money that several subgraphs construct locally and identically.
# Both subgraphs construct Money inline; no entity, no key needed.
extend schema
@link(url: "https://specs.apollo.dev/federation/v2.9",
import: ["@shareable"])
type Money @shareable { # every field shareable in this subgraph
amount: Int!
currencyCode: String!
}
Prefer field-level @shareable for entities and reserve type-level for value types. On an entity, type-level sharing quietly opts every field — including ones you meant to keep single-owned — into multi-resolution, which erodes the ownership signal the contract is supposed to encode. Value types have no @key and no authoritative owner by design, so type-level sharing matches their intent exactly.
How the router routes a shared field
Marking a field shareable does not create one shared resolver. The router keeps strict field-level routing and uses the freedom to minimise hops. For this query:
query GetUserWithOrders($id: ID!) {
user(id: $id) {
email # shareable: auth OR commerce may resolve it
orderHistory { # owned by commerce only
total
}
}
}
Because orderHistory forces a visit to commerce, and commerce can also resolve the @shareable email, the planner fetches both there in a single round-trip instead of making a second hop to auth. That hop reduction is the entire payoff of @shareable. Before applying it, confirm the field truly belongs in more than one subgraph rather than to a single canonical owner — the ownership decision is covered in Type Ownership and Shared Schema Contracts. If only one subgraph should ultimately win, use @override(from: "auth") to assign authority instead, which is the migration-friendly path described in migrating and versioning federated schemas.
Routing Flow Diagram
The diagram shows how a single query resolves a shared field and an owned field through one fetch instead of two.
The solid edge is the single fetch the planner actually issues; the dashed edge to auth is the hop @shareable lets it skip.
Verification Steps
-
Check the subgraph against the registry. Surface conflicts before merge:
rover subgraph check my-graph@prod \ --name commerce \ --schema ./commerce/schema.graphql -
Compose locally. Confirm the merged supergraph builds with zero errors:
rover supergraph compose --config ./supergraph.yaml --output supergraph.graphqlGrep the output SDL for
@join__fieldentries onUser.emailfrom bothauthandcommerce— that confirms the planner recognises both as valid resolvers. -
Query staging and confirm routing. Run the
GetUserWithOrdersquery against the staging router and confirmemailreturns the same value regardless of which subgraph the planner chose. A divergent value means your resolvers are not truly identical and the field should not be shareable.Expected response for a correctly composed shared field:
{ "data": { "user": { "email": "ada@example.com", "orderHistory": [{ "total": 4200 }] } } }If you want to prove the planner is collapsing the hop, enable router debug logging (
APOLLO_ROUTER_LOG=debug) and inspect the query plan: you should see a singleFetchnode againstcommercecovering bothemailandorderHistory, with no separate fetch toauth.
Common Mistakes & Gotchas
- Marking only one subgraph.
@shareablemust appear in every subgraph that resolves the field. One missing annotation reproducesINVALID_FIELD_SHARING. Audit all SDLs, not just the one you changed. - Divergent signatures. Nullability, scalar type, arguments, and defaults must match byte-for-byte.
@shareableauthorises sharing; it never reconciles aString!/Stringmismatch — that still fails withFIELD_TYPE_MISMATCH. - Using it to dodge an ownership decision. If two subgraphs would resolve a field from different sources of truth, they will eventually drift and return inconsistent data. That is a sign the field needs a single owner (or
@override), not@shareable.
Frequently Asked Questions
When should I use @shareable versus extend type with @external?
Use @external when a subgraph references a field it does not resolve — typically a key field, or a dependency for @requires. Use @shareable when multiple subgraphs independently resolve the same field with identical logic and you want the router free to pick whichever minimises hops.
Does @shareable slow down queries?
No. It adds no per-request overhead; it gives the planner more routing options, which usually reduces network hops. Any inconsistency you observe comes from resolvers that produce different values, not from the directive itself.
What if two subgraphs should not both win for a shared field?
Then it should not be @shareable. Assign authority to one subgraph with @override(from: "other-subgraph"), or refactor so a single subgraph defines and resolves the field. @override is also the safe way to migrate ownership over time.
Related
- Type Ownership and Shared Schema Contracts — parent guide on ownership models
- Resolving Schema Conflicts in Apollo Federation — fixing type and directive mismatches at composition
- Defining Subgraph Boundaries for Microservices — deciding which service should own a type
- Migrating and Versioning Federated Schemas — moving field ownership with
@override - GraphQL Federation Architecture & Design — section overview