Apollo Federation subgraph specification
For adding subgraph support to GraphQL server libraries
This content is provided for developers adding federated subgraph support to a GraphQL server library, and for anyone curious about the inner workings of federation. You do not need to read this if you're building a supergraph with existing subgraph-compatible libraries, such as Apollo Server.
Servers that are partially or fully compatible with this specification are tracked in Apollo's subgraph compatibility repository.
For a GraphQL service to operate as an Apollo Federation 2 subgraph, it must do all of the following:
- Automatically extend its schema with all definitions listed in Subgraph schema additions
- Correctly resolve the
Query._service
enhanced introspection field - Provide a mechanism for subgraph developers to resolve entity fields via the
Query._entities
field
Each of these requirements is described in the sections below.
Subgraph schema additions
A subgraph must automatically add all of the following definitions to its GraphQL schema. The purpose of each definition is described in Glossary of schema additions.
If your GraphQL server library is code-first instead of schema-first (i.e., it adds schema definitions programmatically instead of via static SDL), use whatever API is appropriate for your library to generate these definitions on startup.
# ⚠️ This definition must be created dynamically! The union# must include every object type in the schema that uses# the @key directive (i.e., all federated entities).union _Entityscalar _Anyscalar FieldSetscalar link__Importenum link__Purpose {"""`SECURITY` features provide metadata necessary to securely resolve fields."""SECURITY"""`EXECUTION` features provide metadata necessary for operation execution."""EXECUTION}type _Service {sdl: String!}extend type Query {_entities(representations: [_Any!]!): [_Entity]!_service: _Service!}directive @external on FIELD_DEFINITIONdirective @requires(fields: FieldSet!) on FIELD_DEFINITIONdirective @provides(fields: FieldSet!) on FIELD_DEFINITIONdirective @key(fields: FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACEdirective @link(url: String!, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMAdirective @shareable on OBJECT | FIELD_DEFINITIONdirective @inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITIONdirective @override(from: String!) on FIELD_DEFINITIONdirective @composeDirective(name: String!) repeatable on SCHEMA# This definition is required only for libraries that don't support# GraphQL's built-in `extend` keyworddirective @extends on OBJECT | INTERFACE
Enhanced introspection with Query._service
Some federated graph routers can compose their supergraph schema dynamically at runtime. To do so, a graph router first executes the following enhanced introspection query on each of its subgraphs to obtain all subgraph schemas:
query {_service {sdl}}
⚠️ We strongly recommend against dynamic composition in the graph router! Dynamic composition can cause unexpected downtime if composition fails on router startup. Nevertheless, supporting this use case is still a requirement for subgraph libraries.
Differences from built-in introspection
The "enhanced" introspection query above differs from the GraphQL spec's built-in introspection query in the following ways:
- The returned schema representation is a string instead of a
__Schema
object. - The returned schema string includes all uses of federation-specific directives, such as
@key
.- The built-in introspection query's response does not include the uses of any directives.
- The graph router requires these federation-specific directives to perform composition successfully.
- If a subgraph server "disables introspection", the enhanced introspection query is still available.
Note that the _service
field is not included in the composed supergraph schema. For security reasons, it's intended solely for use by the graph router.
Required resolvers for introspection
To support the enhanced introspection query, a subgraph service must define resolvers for the following fields:
extend type Query {_service: _Service!}type _Service {sdl: String!}
Query._service
returns a _Service
object, which in turn has a single field, sdl
(short for schema definition language). The sdl
field returns a string representation of the subgraph's schema.
The returned sdl
string has the following requirements:
- It must include all uses of all federation-specific directives, such as
@key
.- All of these directives are shown in Subgraph schema additions.
- If supporting Federation 1,
sdl
must omit all automatically added definitions from Subgraph schema additions, such asQuery._service
and_Service.sdl
!- If your library is only supporting Federation 2,
sdl
can include these defintions.
- If your library is only supporting Federation 2,
For example, consider this Federation 2 subgraph schema:
extend schema@link(url: "https://specs.apollo.dev/federation/v2.0",import: ["@key"])type Query {me: User}type User @key(fields: "id") {id: ID!}
The value returned for the sdl
field should include all of this information, including directives (excess whitespace can be removed).
Resolving entity fields with Query._entities
In a federated supergraph, an entity is an object type that can define different fields across multiple subgraphs. You can identify an entity in a schema by its use of the @key
directive.
In the following example, the Product
entity defines its fields across the Products and Reviews subgraphs:
type Product @key(fields: "upc") {upc: String!name: String!}
type Product @key(fields: "upc") {upc: String!avgRating: Int!}
If a subgraph contributes any fields to an entity, it must also provide the graph router direct access to the values of those fields. To support this, a subgraph library must do the following:
- Define the
_Entity
union type, which must include every entity type that the subgraph contributes fields to - Provide a mechanism that enables a subgraph developer to identify and return a unique entity instance based on its
@key
fields - Define the
Query._entities
field and resolve it using the mechanism provided to the subgraph developer
These requirements are described further in the sections below.
Defining the _Entity
union
The _Entity
union type is the only schema definition in Subgraph schema additions that a subgraph must generate dynamically based on the schema it's provided. All other definitions are static and can be added exactly as shown.
The _Entity
union must include all entity types that are defined in the subgraph schema, except entities with a @key
that sets resolvable: false
.
If a subgraph defines zero applicable entity types, then it should not define the _Entity
union.
Example
Consider this subgraph schema:
type Review @key(fields: "id") {id: ID!body: Stringauthor: Userproduct: Product}type Product @key(fields: "upc") {upc: String!reviews: [Review!]!}type User @key(fields: "email", resolvable: false) {email: String!}
All three of the types in this subgraph schema are entities (note their @key
directives). However, the User
entity's @key
sets resolvable: false
. Therefore, the subgraph library should add the following _Entity
union definition to the schema:
# Omits `User` because its @key sets resolvable: falseunion _Entity = Review | Product
The _Entity
union is used by the Query._entities
field, which is covered next.
Understanding Query._entities
If a subgraph contributes fields to at least one entity, it must automatically define and correctly resolve the Query._entities
field:
type Query {_entities(representations: [_Any!]!): [_Entity]!}
If a subgraph doesn't define any entity types, then it should not define the Query._entities
field.
The graph router uses this entry point to directly fetch fields of entity objects. It combines those fields with other fields of the same entity that are returned by other subgraphs.
The Query._entities
field takes a required representations
argument, which is a list of entity representations. A representation is an object that contains all fields from one of an entity's @key
s, plus that entity's __typename
field. These are the fields that a subgraph requires to uniquely identify a particular instance of an entity.
Each item in the representations
list is an _Any
scalar. This is a federation-specific scalar defined in Subgraph schema additions. This scalar is serialized as a generic JSON object, which enables the graph router to include representations of different entities in the same query, all of which can have a different shape.
Here's an example _Any
representation for a Product
entity:
{"__typename": "Product","upc": "abc123"}
The Query._entities
field must return a list of entity objects that correspond to the provided representations, in the exact same order. Entries in the list can be null if no entity exists for a provided representation.
Example
Let's say a supergraph includes two subgraphs with the following schemas:
type Product @key(fields: "upc") {upc: String!name: String!}type Query {topProducts: [Product!]!}
type Product @key(fields: "upc") {upc: String!reviews: [Review!]!}type Review {score: Int!description: String!}
With these subgraph schemas, a client can execute the following query against the graph router:
query GetTopProductReviews {topProducts {reviews {description}}}
To resolve this query, the graph router starts by sending the following query to the Products subgraph, because that's where the top-level Query.topProducts
field is defined:
query {topProducts {__typenameupc}}
Notice that this query includes Product.__typename
and Product.upc
, even though those fields aren't included in the original client query. The graph router knows that these two fields are used in a Product
type's representation, which it will use to fetch the remaining fields from the Reviews subgraph.
After getting this result from the Products subgraph, the router can send this followup query to the Reviews subgraph:
query ($_representations: [_Any!]!) {_entities(representations: $_representations) {... on Product {reviews {description}}}}
Notice that this query uses inline fragment matching (... on Product
), because the return type of Query._entities
is the _Entity
union type.
Each entry that the router includes in the $_representations
list variable has the following shape:
{"__typename": "Product","upc": "B00005N5PF"}
These are the representation fields that the router obtained from its Products query above.
Resolving Query._entities
As a reminder, here's the definition of the Query._entities
field that every subgraph must automatically define (unless a subgraph contributes fields to zero entities):
type Query {_entities(representations: [_Any!]!): [_Entity]!}
Every subgraph must also automatically define the resolver for this field. The logic for this resolver is as follows:
- Create an empty array that will contain the entity objects to return.
- For each entity representation included in the
representations
list:- Obtain the entity's
__typename
from the representation. - Pass the full representation object to whatever mechanism the library provides the subgraph developer for fetching entities of the corresponding
__typename
. - Add the fetched entity object to the array of entity objects. Make sure objects are listed in the same order as their corresponding representations.
- Obtain the entity's
- Return the array of entity objects.
Notice in step 2.2 above that the subgraph developer is responsible for defining logic that fetches a particular entity based on its representation. The subgraph library is responsible for providing the mechanism that developers use to specify this logic, and for automatically hooking into this mechanism in the resolver for Query._entities
.
See the next section for more details on providing this mechanism.
Providing a mechanism for fetching entities
When using your subgraph library, a developer must be able to specify logic for fetching a unique entity instance based on a corresponding representation of that entity. The automatically defined resolver for Query._entities
must then hook into this logic.
For example, let's look at how Apollo Server (with the @apollo/subgraph
library) enables this via reference resolvers.
In Apollo Server, developers can add a special function named __resolveReference
to every entity type that's defined in their resolver map:
// Products subgraphconst resolvers = {Product: {__resolveReference(productRepresentation) {return fetchProductByUPC(productRepresentation.upc);}},// ...other resolvers...}
The Query._entities
resolver iterates through the representations
it's passed and executes the corresponding __resolveReference
function for each one. It passes the representation object as the first parameter to the function.
The representation object passed to the reference resolver above might have the following structure:
{"__typename": "Product","upc": "B00005N5PF"}
For this reference resolver, the developer calls a fetchProductByUPC
function, passing the upc
from the representation. This function might query a database or a REST API to fetch the entity fields of Product
that this subgraph knows about.
Your subgraph library does not need to use this reference resolver pattern. It just needs to provide and document some pattern for defining entity-fetching logic.
Glossary of schema additions
This section describes type and field definitions that a valid subgraph service must automatically add to its schema. These definitions are all listed above in Subgraph schema additions.
For descriptions of added directives, see Federation-specific GraphQL directives.
Query
fields
Query._service
This field of the root Query
type must return a non-nullable _Service
type.
For details, see Enhanced introspection with Query._service
.
Query._entities
The graph router uses this root-level Query
field to directly fetch fields of entities defined by a subgraph.
This field must take a representations
argument of type [_Any!]!
(a non-nullable list of non-nullable _Any
scalars). Its return type must be [_Entity]!
(a non-nullable list of nullable objects that belong to the _Entity
union).
Each entry in the representations
list must be validated with the following rules:
- A representation must include a
__typename
string field. - A representation must contain all fields included in the fieldset of a
@key
directive applied to the corresponding entity definition.
For details, see Resolving entity fields with Query._entities
.
Types
type _Service
This object type must have an sdl: String!
field, which returns the SDL of the subgraph schema as a string.
- The returned schema string must include all uses of federation-specific directives (
@key
,@requires
, etc.). - If supporting Federation 1, the schema must not include any definitions from Subgraph schema additions.
For details, see Enhanced introspection with Query._service
.
union _Entity
⚠️ This union type is generated dynamically based on the input subgraph schema!
This union's possible types must include all entities that the subgraph defines. It's the return type of the Query._entities
field, which the graph router uses to directly access a subgraph's entity fields.
For details, see Defining the _Entity
union.
scalar _Any
This scalar is the type used for entity representations that the graph router passes to the Query._entities
field. An _Any
scalar is validated by matching its __typename
and @key
fields against entities defined in the subgraph schema.
An _Any
is serialized as a JSON object, like so:
{"__typename": "Product","upc": "abc123"}
scalar FieldSet
This string-serialized scalar represents a set of fields that's passed to a federated directive, such as @key
, @requires
, or @provides
.
Grammatically, a FieldSet
is a selection set minus the outermost curly braces. It can represent a single field ("upc"
), multiple fields ("id countryCode"
), and even nested selection sets ("id organization { id }"
).