Apollo Federation Subgraph Specification

Subgraph specification reference for server library developers


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:

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.

 note
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.
GraphQL
1# ⚠️ This definition must be created dynamically. The union
2#   must include every object type in the schema that uses
3#   the @key directive (i.e., all federated entities).
4union _Entity
5
6scalar _Any
7scalar FieldSet
8scalar link__Import
9scalar federation__ContextFieldValue
10scalar federation__Scope
11scalar federation__Policy
12
13enum link__Purpose {
14  """
15  `SECURITY` features provide metadata necessary to securely resolve fields.
16  """
17  SECURITY
18
19  """
20  `EXECUTION` features provide metadata necessary for operation execution.
21  """
22  EXECUTION
23}
24
25type _Service {
26  sdl: String!
27}
28
29extend type Query {
30  _entities(representations: [_Any!]!): [_Entity]!
31  _service: _Service!
32}
33
34directive @external on FIELD_DEFINITION | OBJECT
35directive @requires(fields: FieldSet!) on FIELD_DEFINITION
36directive @provides(fields: FieldSet!) on FIELD_DEFINITION
37directive @key(fields: FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE
38directive @link(url: String!, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA
39directive @shareable repeatable on OBJECT | FIELD_DEFINITION
40directive @inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION
41directive @tag(name: String!) repeatable on FIELD_DEFINITION | INTERFACE | OBJECT | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION
42directive @override(from: String!) on FIELD_DEFINITION
43directive @composeDirective(name: String!) repeatable on SCHEMA
44directive @interfaceObject on OBJECT
45directive @authenticated on FIELD_DEFINITION | OBJECT | INTERFACE | SCALAR | ENUM
46directive @requiresScopes(scopes: [[federation__Scope!]!]!) on FIELD_DEFINITION | OBJECT | INTERFACE | SCALAR | ENUM
47directive @policy(policies: [[federation__Policy!]!]!) on FIELD_DEFINITION | OBJECT | INTERFACE | SCALAR | ENUM
48directive @context(name: String!) repeatable on INTERFACE | OBJECT | UNION
49directive @fromContext(field: ContextFieldValue) on ARGUMENT_DEFINITION
50
51# This definition is required only for libraries that don't support
52# GraphQL's built-in `extend` keyword
53directive @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:

GraphQL
1query {
2  _service {
3    sdl
4  }
5}
⚠️ caution
Apollo strongly recommends 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
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:

GraphQL
1extend type Query {
2  _service: _Service!
3}
4
5type _Service {
6  sdl: String!
7}

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.

  • If supporting Federation 1, sdl must omit all automatically added definitions from Subgraph schema additions, such as Query._service and _Service.sdl!

    • If your library is only supporting Federation 2, sdl can include these defintions.

For example, consider this Federation 2 subgraph schema:

GraphQL
schema.graphql
1extend schema
2  @link(url: "https://specs.apollo.dev/federation/v2.3",
3        import: ["@key"])
4
5type Query {
6  me: User
7}
8
9type User @key(fields: "id") {
10  id: ID!
11}

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:

GraphQL
Products subgraph
1type Product @key(fields: "upc") {
2  upc: String!
3  name: String!
4}
GraphQL
Reviews subgraph
1type Product @key(fields: "upc") {
2  upc: String!
3  avgRating: Int!
4}

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.

 note
If a subgraph defines zero applicable entity types, then it should not define the _Entity union.

Example

Consider this subgraph schema:

GraphQL
Reviews subgraph
1type Review @key(fields: "id") {
2  id: ID!
3  body: String
4  author: User
5  product: Product
6}
7
8type Product @key(fields: "upc") {
9  upc: String!
10  reviews: [Review!]!
11}
12
13type User @key(fields: "email", resolvable: false) {
14  email: String!
15}

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:

GraphQL
1# Omits `User` because its @key sets resolvable: false
2union _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:

GraphQL
1type Query {
2  _entities(representations: [_Any!]!): [_Entity]!
3}
 note
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 @keys, 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:

JSON
1{
2  "__typename": "Product",
3  "upc": "abc123"
4}

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:

GraphQL
Products
1type Product @key(fields: "upc") {
2  upc: String!
3  name: String!
4}
5
6type Query {
7  topProducts: [Product!]!
8}
GraphQL
Reviews
1type Product @key(fields: "upc") {
2  upc: String!
3  reviews: [Review!]!
4}
5
6type Review {
7  score: Int!
8  description: String!
9}

With these subgraph schemas, a client can execute the following query against the graph router:

GraphQL
1query GetTopProductReviews {
2  topProducts {
3    reviews {
4      description
5    }
6  }
7}

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:

GraphQL
1query {
2  topProducts {
3    __typename
4    upc
5  }
6}

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:

GraphQL
1query ($_representations: [_Any!]!) {
2  _entities(representations: $_representations) {
3    ... on Product {
4      reviews {
5        description
6      }
7    }
8  }
9}

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:

JSON
1{
2  "__typename": "Product",
3  "upc": "B00005N5PF"
4}

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):

GraphQL
1type Query {
2  _entities(representations: [_Any!]!): [_Entity]!
3}

Every subgraph must also automatically define the resolver for this field. The logic for this resolver is as follows:

  1. Create an empty array that will contain the entity objects to return.

  2. For each entity representation included in the representations list:

    1. Obtain the entity's __typename from the representation.

    2. Pass the full representation object to whatever mechanism the library provides the subgraph developer for fetching entities of the corresponding __typename.

    3. Add the fetched entity object to the array of entity objects. Make sure objects are listed in the same order as their corresponding representations.

  3. 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:

JavaScript
resolvers.js
1// Products subgraph
2const resolvers = {
3  Product: {
4    __resolveReference(productRepresentation) {
5      return fetchProductByUPC(productRepresentation.upc);
6    }
7  },
8  // ...other resolvers...
9}

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:

JSON
1{
2  "__typename": "Product",
3  "upc": "B00005N5PF"
4}

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

 note
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:

JSON
1{
2  "__typename": "Product",
3  "upc": "abc123"
4}

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 }").

scalar Scope

This string-serialized scalar represents a JWT scope.

scalar Policy

This string-serialized scalar represents an authorization policy.

Directives

See Federation-specific GraphQL directives.

Feedback

Edit on GitHub

Forums