Subchapter 3.1
references/composition.mdMarkdown8 KBView on GitHub
Rules for composing subgraph schemas into a supergraph, with error codes and fixes.
A subgraph’s @link(url: ".../federation/vX.Y") version is the minimum
version required for the directives that subgraph uses — not a declaration
of what version the graph is composed at. Two versions are in play, and they are
set independently:
@link version in each subgraph’s SDL. It just needs
to cover the directives that subgraph actually uses.federation_version in supergraph.yaml for local composition, or the
variant’s Build Pipeline setting in GraphOS.The only rule between them is: the composition version must be ≥ every
subgraph’s floor. A subgraph sitting below the composition version is normal,
not a bug — you do not need to bump every subgraph’s @link to match the
composition version.
The version numbers used throughout this skill (e.g.
v2.12,=2.9.0) are illustrative. Check the Federation changelog (opens in a new tab) for currently supported versions before pinning one, rather than copying whatever number appears in an example.
If buildSubgraphSchema() throws UNKNOWN_FEDERATION_LINK_VERSION at server
startup (not at rover subgraph publish / composition time), that's a
client-library lag — not a sign that GraphOS doesn’t support the version.
Composition (Rust, in Rover/Router) and the JS schema-building library you build
your subgraph with (@apollo/subgraph) have independent, separately-versioned
understandings of which federation versions exist, and the JS side can trail
behind. So a version can compose fine in GraphOS yet be rejected by your local
subgraph server.
Fix: lower that subgraph’s @link to the highest version your library
actually recognizes (upgrade @apollo/subgraph if you need a newer one). The
composition version can still be pinned higher — remember the floor-vs-composition
distinction above.
Entities must have valid @key definitions that can be resolved across subgraphs.
@key includes a field returning list, interface, or union.
# INVALID
type Product @key(fields: "tags") {
tags: [String!]! # list not allowed in key
}
# VALID
type Product @key(fields: "id") {
id: ID!
tags: [String!]!
}Use only scalar, enum, or object fields in keys.
@key includes a field with arguments.
# INVALID
type Product @key(fields: "name") {
name(locale: String!): String! # args not allowed in key
}
# VALID - use a field without arguments
type Product @key(fields: "id") {
id: ID!
name(locale: String!): String!
}Invalid syntax or unknown fields in @key.
# INVALID
type Product @key(fields: "sku") {
id: ID! # "sku" doesn't exist
}
# VALID
type Product @key(fields: "id") {
id: ID!
}Check field names and syntax: @key(fields: "id") or @key(fields: "id organization { id }").
Entity interface has @key but an implementation doesn’t.
# INVALID
interface Media @key(fields: "id") {
id: ID!
}
type Book implements Media { # missing @key
id: ID!
}
# VALID
type Book implements Media @key(fields: "id") {
id: ID!
}All implementations must have the same @key(s) as the interface.
Fields resolved by multiple subgraphs must be explicitly marked @shareable.
Field resolved by multiple subgraphs without @shareable.
# INVALID
type Position {
x: Int!
}
# VALID
type Position @shareable {
x: Int!
}Add @shareable to the field or type in all subgraphs.
Shareable field has incompatible types across subgraphs.
# INVALID
# Subgraph A
type Event @shareable {
timestamp: Int!
}
# Subgraph B
type Event @shareable {
timestamp: String! # incompatible with Int!
}Nullable can coerce to non-nullable, but base types must be compatible.
Fields marked @external must exist in another subgraph and be used by a directive.
@external field not defined in any other subgraph.
Define the field in the originating subgraph, or remove @external.
@external field not used by @key, @requires, or @provides.
Either use the field in a directive or remove it.
@external field type doesn’t match the original definition.
Align the type with the originating subgraph.
Fields referenced in @provides and @requires must be properly declared as @external.
@provides field not marked @external.
# INVALID
type Product @key(fields: "id") {
id: ID!
name: String! # missing @external
}
type Query {
products: [Product!]! @provides(fields: "name")
}
# VALID
type Product @key(fields: "id") {
id: ID!
name: String! @external
}
type Query {
products: [Product!]! @provides(fields: "name")
}@requires field not marked @external.
# INVALID
type Product @key(fields: "id") {
id: ID!
weight: Int # missing @external
shippingCost: Int @requires(fields: "weight")
}
# VALID
type Product @key(fields: "id") {
id: ID!
weight: Int @external
shippingCost: Int @requires(fields: "weight")
}The @override directive has strict rules about which fields it can be applied to.
@override(from: "...") references its own subgraph.
Use the name of the other subgraph.
Overridden field also has @override applied.
Only one subgraph can override a field at a time.
@override used with @external, @provides, or @requires.
Cannot override external or provided/required fields.
Types with the same name across subgraphs must be compatible.
Same field has incompatible types across subgraphs.
Align types. Nullable fields can accept non-nullable, but not vice versa.
Same type name but different kinds (e.g., object vs interface).
Use consistent type definitions across subgraphs.
Enum has no values common to all subgraphs.
Ensure at least one shared value, or use @inaccessible for subgraph-specific values.
The @inaccessible directive hides elements from the API schema but has constraints.
@inaccessible element referenced by a visible element.
Also mark the referencing element @inaccessible, or remove @inaccessible.
Type has only @inaccessible fields.
Add at least one accessible field to the type.
Query cannot be satisfied by available subgraphs. Common causes:
@key on entityresolvable: false when resolution is neededEnsure a traversable path exists between subgraphs for every possible query.
rover supergraph compose --config supergraph.yaml locallyrover subgraph check to validate against production@key fields are consistent across subgraphs@external fields exist in originating subgraph