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.displayName or Product.sku that 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, @shareable is the wrong tool; consolidate ownership or use @override instead.

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.

Router fetching a shareable field and an owned field in one hop A query for email and orderHistory is planned by the router. Because commerce can resolve the shareable email and owns orderHistory, the router fetches both from commerce in a single round-trip rather than also calling auth. router plans: email + orderHistory auth (owner) email @shareable not visited here commerce email @shareable orderHistory (owned) one fetch resolves both skipped

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

  1. Check the subgraph against the registry. Surface conflicts before merge:

    rover subgraph check my-graph@prod \
      --name commerce \
      --schema ./commerce/schema.graphql
  2. Compose locally. Confirm the merged supergraph builds with zero errors:

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

    Grep the output SDL for @join__field entries on User.email from both auth and commerce — that confirms the planner recognises both as valid resolvers.

  3. Query staging and confirm routing. Run the GetUserWithOrders query against the staging router and confirm email returns 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 single Fetch node against commerce covering both email and orderHistory, with no separate fetch to auth.

Common Mistakes & Gotchas

  • Marking only one subgraph. @shareable must appear in every subgraph that resolves the field. One missing annotation reproduces INVALID_FIELD_SHARING. Audit all SDLs, not just the one you changed.
  • Divergent signatures. Nullability, scalar type, arguments, and defaults must match byte-for-byte. @shareable authorises sharing; it never reconciles a String!/String mismatch — that still fails with FIELD_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.