GraphQL Composite Schemas Spec

Introduction

The GraphQL Composite Schemas Spec introduces a comprehensive specification for creating distributed GraphQL systems that seamlessly merges multiple GraphQL schemas. This specification describes the process of composing a federated GraphQL schema and outlines algorithms for executing GraphQL queries on the federated schema effectively by using query plans. This specification was originally created by ChilliCream and was transferred to the GraphQL foundation.

The GraphQL Foundation was formed in 2019 as a neutral focal point for organizations who support the GraphQL ecosystem, and the GraphQL Specification Project was established also in 2019 as the Joint Development Foundation Projects, LLC, GraphQL Series.

If your organization benefits from GraphQL, please consider becoming a member and helping us to sustain the activities that support the health of our neutral ecosystem.

The GraphQL Specification Project has evolved and may continue to evolve in future editions of this specification. Previous editions of the GraphQL specification can be found at permalinks that match their release tag. The latest working draft release can be found at https://spec.graphql.org/draft.

Conformance

A conforming implementation of the GraphQL Composite Schemas Spec must fulfill all normative requirements. Conformance requirements are described in this document via both descriptive assertions and key words with clearly defined meanings.

The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in the normative portions of this document are to be interpreted as described in IETF RFC 2119. These key words may appear in lowercase and still retain their meaning unless explicitly declared as non-normative.

A conforming implementation of the GraphQL Composite Schemas Spec may provide additional functionality, but must not where explicitly disallowed or would otherwise result in non-conformance.

Non-Normative Portions

All contents of this document are normative except portions explicitly declared as non-normative.

Examples in this document are non-normative, and are presented to aid understanding of introduced concepts and the behavior of normative portions of the specification. Examples are either introduced explicitly in prose (e.g. “for example”) or are set apart in example or counter-example blocks, like this:

Example № 1This is an example of a non-normative example.
Counter Example № 2This is an example of a non-normative counter-example.

Notes in this document are non-normative, and are presented to clarify intent, draw attention to potential edge-cases and pit-falls, and answer common questions that arise during implementation. Notes are either introduced explicitly in prose (e.g. “Note: “) or are set apart in a note block, like this:

Note This is an example of a non-normative note.

1Overview

The GraphQL Composite Schemas specification describes how to construct a single unified GraphQL schema, the composite schema, from multiple GraphQL schemas, each termed a source schema.

The composite schema presents itself as a regular GraphQL schema; the implementation details and complexities of the underlying distributed systems are not visible to clients, all observable behavior is the same as described by the GraphQL specification.

The GraphQL Composite Schemas specification has a number of design principles:

To enable greater interoperability between different implementations of tooling and gateways, this specification focuses on two core components: schema composition and distributed execution.

2Source Schema

A source schema is a GraphQL schema that is part of a larger composite schema. Source schemas use directives to express intent and requirements for the composition process. In the following chapters, we will describe the directives that are used to annotate a source schema.

2.1Directives

2.1.1@lookup

directive @lookup on FIELD_DEFINITION

The @lookup directive is used within a source schema to specify output fields that can be used by the distributed GraphQL executor to resolve an entity by a stable key.

The stable key is defined by the arguments of the field. Each argument must match a field on the return type of the lookup field.

Source schemas can provide multiple lookup fields for the same entity that resolve the entity by different keys.

In this example, the source schema specifies that the Product entity can be resolved with the productById field or the productByName field. Both lookup fields are able to resolve the Product entity but do so with different keys.

Example № 3type Query {
  version: Int # NOT a lookup field.
  productById(id: ID!): Product @lookup
  productByName(name: String!): Product @lookup
}

type Product @key(fields: "id") @key(fields: "name") {
  id: ID!
  name: String!
}

The arguments of a lookup field must correspond to fields specified as an entity key with the @key directive on the entity type.

Example № 4type Query {
  node(id: ID!): Node @lookup
}

interface Node @key(fields: "id") {
  id: ID!
}

Lookup fields may return object, interface, or union types. In case a lookup field returns an abstract type (interface type or union type), all possible object types are considered entities and must have keys that correspond with the field’s argument signature.

Example № 5type Query {
  product(id: ID!, categoryId: Int): Product @lookup
}

union Product = Electronics | Clothing

type Electronics @key(fields: "id categoryId") {
  id: ID!
  categoryId: Int
  name: String
  brand: String
  price: Float
}

type Clothing @key(fields: "id categoryId") {
  id: ID!
  categoryId: Int
  name: String
  size: String
  price: Float
}

The following example shows an invalid lookup field as the Clothing type does not declare a key that corresponds with the lookup field’s argument signature.

Counter Example № 6type Query {
  product(id: ID!, categoryId: Int): Product @lookup
}

union Product = Electronics | Clothing

type Electronics @key(fields: "id categoryId") {
  id: ID!
  categoryId: Int
  name: String
  brand: String
  price: Float
}

# Clothing does not have a key that corresponds
# with the lookup field's argument signature.
type Clothing @key(fields: "id") {
  id: ID!
  categoryId: Int
  name: String
  size: String
  price: Float
}

If the lookup returns an interface, the interface must also be annotated with a @key directive and declare its keys.

Example № 7interface Node @key(fields: "id") {
  id: ID!
}

Lookup fields must be accessible from the Query type. If not directly on the Query type, they must be accessible via fields that do not require arguments, starting from the Query root type.

Example № 8type Query {
  lookups: Lookups!
}

type Lookups {
  productById(id: ID!): Product @lookup
}

type Product @key(fields: "id") {
  id: ID!
}

Lookups can also be nested within other lookups and allow resolving nested entities that are part of an aggregate. In the following example the Product can be resolved by its ID but also the ProductPrice can be resolved by passing in a composite key containing the product ID and region name of the product price.

Example № 9type Query {
  productById(id: ID!): Product @lookup
}

type Product @key(fields: "id") {
  id: ID!
  price(regionName: String!): ProductPrice @lookup
}

type ProductPrice @key(fields: "regionName product { id }") {
  regionName: String!
  product: Product
  value: Float!
}

Nested lookups must immediately follow the parent lookup and cannot be nested with fields in between.

Counter Example № 10type Query {
  productById(id: ID!): Product @lookup
}

type Product @key(fields: "id") {
  id: ID!
  details: ProductDetails
}

type ProductDetails {
  price(regionName: String!): ProductPrice @lookup
}

type ProductPrice @key(fields: "regionName product { id }") {
  regionName: String!
  product: Product
  value: Float!
}

2.1.2@internal

directive @internal on FIELD_DEFINITION

The @internal directive is used to mark lookup fields as internal. Internal lookup fields are not used as entry points in the composite schema and can only be used by the distributed GraphQL executor to resolve additional data for an entity.

Example № 11type Query {
  # lookup field and possible entry point
  reviewById(id: ID!): Review @lookup

  # internal lookup field
  productById(id: ID!): Product @lookup @internal
}

The @internal directive provides control over which source schemas are used to resolve entities and which source schemas merely contribute data to entities. Further, using @internal allows hiding “technical” lookup fields that are not meant for the client-facing composite schema.

2.1.3@is

directive @is(field: FieldSelectionMap!) on ARGUMENT_DEFINITION

The @is directive is utilized on lookup fields to describe how the arguments can be mapped from the entity type that the lookup field resolves. The mapping establishes semantic equivalence between disparate type system members across source schemas and is used in cases where the argument does not 1:1 align with a field on the entity type.

In the following example, the directive specifies that the id argument on the field Query.personById and the field Person.id on the return type of the field are semantically the same.

Note In this case the @is directive could also be omitted as the argument and field names match.
Example № 12extend type Query {
  personById(id: ID! @is(field: "id")): Person @lookup
}

The @is directive also allows referring to nested fields relative to Person.

Example № 13extend type Query {
  personByAddressId(id: ID! @is(field: "address.id")): Person
}

The @is directive is not limited to a single argument.

Example № 14extend type Query {
  personByAddressId(
    id: ID! @is(field: "address.id")
    kind: PersonKind @is(field: "kind")
  ): Person
}

The @is directive can also be used in combination with @oneOf to specify lookup fields that can resolve entities by different keys.

Example № 15extend type Query {
  person(
    by: PersonByInput
      @is(field: "{ id } | { addressId: address.id } | { name }")
  ): Person
}

input PersonByInput @oneOf {
  id: ID
  addressId: ID
  name: String
}
Arguments:
  • field: Represents a selection path map syntax.

2.1.4@require

directive @require(field: FieldSelectionMap!) on ARGUMENT_DEFINITION

The @require directive is used to express data requirements with other source schemas. Arguments annotated with the @require directive are removed from the composite schema and the value for these will be resolved by the distributed executor.

Example № 16type Product {
  id: ID!
  delivery(
    zip: String!
    size: Int! @require(field: "dimension.size")
    weight: Int! @require(field: "dimension.weight")
  ): DeliveryEstimates
}

The above example would translate to the following in the composite schema.

Example № 17type Product {
  id: ID!
  delivery(zip: String!): DeliveryEstimates
}

This can also be done by using input types. The selection path map specifies which data is required and needs to be resolved from other source schemas. If the input type is only used to express requirements it is removed from the composite schema.

Example № 18type Product {
  id: ID!
  delivery(
    zip: String!
    dimension: ProductDimensionInput! @require(field: "{ size: dimension.size, weight: dimension.weight }"))
  ): DeliveryEstimates
}

If the input types do not match the output type structure the selection map syntax can be used to specify how requirements translate to the input object.

Example № 19type Product {
  id: ID!
  delivery(
    zip: String!
    dimension: ProductDimensionInput!
      @require(field: "{ productSize: dimension.size, productWeight: dimension.weight }"))
  ): DeliveryEstimates
}

type ProductDimension {
  size: Int!
  weight: Int!
}

input ProductDimensionInput {
  productSize: Int!
  productWeight: Int!
}
Arguments:
  • field: Represents a selection path map syntax.

2.1.5@key

directive @key(fields: SelectionSet!) repeatable on OBJECT | INTERFACE

The @key directive is used to designate an entity’s unique key, which identifies how to uniquely reference an instance of an entity across different source schemas. It allows a source schema to indicate which fields form a unique identifier, or key, for an entity.

Example № 20type Product @key(fields: "id") {
  id: ID!
  sku: String!
  name: String!
  price: Float!
}

Each occurrence of the @key directive on an object or interface type specifies one distinct unique key for that entity, which enables a gateway to perform lookups and resolve instances of the entity based on that key.

Example № 21type Product @key(fields: "id") @key(fields: "key") {
  id: ID!
  sku: String!
  name: String!
  price: Float!
}

While multiple keys define separate ways to reference the same entity based on different sets of fields, a composite key allows for uniquely identifying an entity by using a combination of multiple fields.

Example № 22type Product @key(fields: "id sku") {
  id: ID!
  sku: String!
  name: String!
  price: Float!
}

The directive is applicable to both OBJECT and INTERFACE types. This allows entities that implement an interface to inherit the key(s) defined at the interface level, ensuring consistent identification across different implementations of that interface.

Arguments:
  • fields: Represents a selection set syntax.

2.1.6@shareable

directive @shareable repeatable on OBJECT | FIELD_DEFINITION

By default, only a single source schema is allowed to contribute a particular field to an object type. This prevents source schemas from inadvertently defining similarly named fields that are semantically not the same.

Fields have to be explicitly marked as @shareable to allow multiple source schemas to define it, and this ensures that the step of allowing a field to be served from multiple source schemas is an explicit, coordinated decision.

If multiple source schemas define the same field, these are assumed to be semantically equivalent, and the executor is free to choose between them as it sees fit.

Note Key fields are always considered sharable.

2.1.7@provides

directive @provides(fields: SelectionSet!) on FIELD_DEFINITION

The @provides directive is an optimization hint specifying child fields that can be resolved locally at the given source schema through a particular query path. This allows for a variation of overlapping fields to improve data fetching.

Arguments:
  • fields: Represents a selection set syntax.

2.1.8@external

directive @external on OBJECT_DEFINITION | INTERFACE_DEFINITION | FIELD_DEFINITION

The @external directive is used in combination with the @provides directive and specifies data that is not owned by a particular source schema.

2.1.9@override

directive @override(from: String!) on FIELD_DEFINITION

The @override directive allows for migrating fields from one source schema to another.

3Schema Composition

The schema composition describes the process of merging multiple source schemas into a single GraphQL schema, known as the composite execution schema, which is a valid GraphQL schema annotated with execution directives. This composite execution schema is the output of the schema composition process. The schema composition process is divided into three main steps: Validate Source Schemas, Merge Source Schemas, and Validate Satisfiability, which are run in sequence to produce the composite execution schema.

3.1Validate Source Schemas

3.2Merge Source Schemas

3.2.1Pre Merge Validation

3.2.1.1Enum Type Default Value Uses Inaccessible Value

Error Code

ENUM_TYPE_DEFAULT_VALUE_INACCESSIBLE

Formal Specification
ValidateArgumentDefaultValues()
  1. Let arguments be all arguments of fields and directives across all source schemas
  2. For each argument in arguments
    1. If IsExposed(argument) is true and has a default value:
      1. Let defaultValue be the default value of argument
      2. If not ValidateDefaultValue(defaultValue)
        1. return false
  3. return true
ValidateInputFieldDefaultValues()
  1. Let inputFields be all input fields across all source schemas
  2. For each inputField in inputFields:
    1. Let type be the type of inputField
    2. If IsExposed(inputField) is true and inputField has a default value:
      1. Let defaultValue be the default value of inputField
      2. If ValidateDefaultValue(defaultValue) is false
        1. return false
  3. return true
ValidateDefaultValue(defaultValue)
  1. If defaultValue is a ListValue:
    1. For each valueNode in defaultValue:
      1. If ValidateDefaultValue(valueNode) is false
        1. return false
  2. If defaultValue is an ObjectValue:
    1. Let objectFields be a list of all fields of defaultValue
    2. Let fields be a list of all fields objectFields are referring to
    3. For each field in fields:
      1. If IsExposed(field) is false
        1. return false
    4. For each objectField in objectFields:
      1. Let value be the value of objectField
      2. return ValidateDefaultValue(value)
  3. If defaultValue is an EnumValue:
    1. If IsExposed(defaultValue) is false
      1. return false
  4. return true
Explanatory Text

This rule ensures that inaccessible enum values are not exposed in the composed schema through default values. Output field arguments, input fields, and directive arguments must only use enum values as their default value when not annotated with the @inaccessible directive.

In this example the FOO value in the Enum1 enum is not marked with @inaccessible, hence it does not violate the rule.

type Query {
  field(type: Enum1 = FOO): [Baz!]!
}

enum Enum1 {
  FOO
  BAR
}

The following example violates this rule because the default value for the field field in type Input1 references an enum value (FOO) that is marked as @inaccessible.

Counter Example № 23type Query {
  field(arg: Enum1 = FOO): [Baz!]!
}

input Input1 {
  field: Enum1 = FOO
}

directive @directive1(arg: Enum1 = FOO) on FIELD_DEFINITION

enum Enum1 {
  FOO @inaccessible
  BAR
}
Counter Example № 24type Query {
  field(arg: Input1 = { field2: "ERROR" }): [Baz!]!
}

directive @directive1(arg: Input1 = { field2: "ERROR" }) on FIELD_DEFINITION

input Input1 {
  field1: String
  field2: String @inaccessible
}

3.2.1.2Output Field Types Mergeable

Error Code

OUTPUT_FIELD_TYPES_NOT_MERGEABLE

Severity

ERROR

Formal Specification
  • Let typeNames be the set of all output type names from all source schemas.
  • For each typeName in typeNames
    • Let types be the set of all types with the name typeName from all source schemas.
    • Let fieldNames be the set of all field names from all types.
    • For each fieldName in fieldNames
      • Let fields be the set of all fields with the name fieldName from all types.
      • FieldsAreMergeable(fields) must be true.
FieldsAreMergeable(fields)
  1. Given each pair of members fieldA and fieldB in fields:
    1. Let typeA be the type of fieldA
    2. Let typeB be the type of fieldB
    3. SameTypeShape(typeA, typeB) must be true.
Explanatory Text

Fields on objects or interfaces that have the same name are considered semantically equivalent and mergeable when they have a mergeable field type.

Fields with the same type are mergeable.

Example № 25type User {
  birthdate: String
}

type User {
  birthdate: String
}

Fields with different nullability are mergeable, resulting in a merged field with a nullable type.

Example № 26type User {
  birthdate: String!
}

type User {
  birthdate: String
}
Example № 27type User {
  tags: [String!]
}

type User {
  tags: [String]!
}

type User {
  tags: [String]
}

Fields are not mergeable if the named types are different in kind or name.

Counter Example № 28type User {
  birthdate: String!
}

type User {
  birthdate: DateTime!
}
Counter Example № 29type User {
  tags: [Tag]
}

type Tag {
  value: String
}

type User {
  tags: [Tag]
}

scalar Tag

3.2.1.3Disallowed Inaccessible Elements

Error Code

DISALLOWED_INACCESSIBLE

Severity

ERROR

Formal Specification
  • Let type be the set of all types from all source schemas.
  • For each type in types:
    • If type is a built-in scalar type or introspection type:
      • IsAccessible(type) must be true.
      • For each field in type:
        • IsAccessible(field) must be true.
        • For each argument in field:
          • IsAccessible(argument) must be true.
  • For each directive in directives:
    • If directive is a built-in directive:
      • IsAccessible(directive) must be true.
      • For each argument in directive:
        • IsAccessible(argument) must be true.
Explanatory Text

This rule ensures that certain essential elements of a GraphQL schema, particularly built-in scalars, directives and introspection types, cannot be marked as @inaccessible. These types are fundamental to GraphQL. Making these elements inaccessible would break core GraphQL functionality.

Here, the String type is not marked as @inaccessible, which adheres to the rule:

Example № 30type Product {
  price: Float
  name: String
}

In this example, the String scalar is marked as @inaccessible. This violates the rule because String is a required built-in type that cannot be inaccessible:

Counter Example № 31scalar String @inaccessible

type Product {
  price: Float
  name: String
}

In this example, the introspection type __Type is marked as @inaccessible. This violates the rule because introspection types must remain accessible for GraphQL introspection queries to work.

Counter Example № 32type __Type @inaccessible {
  kind: __TypeKind!
  name: String
  fields(includeDeprecated: Boolean = false): [__Field!]
}

3.2.1.4External Argument Default Mismatch

Error Code

EXTERNAL_ARGUMENT_DEFAULT_MISMATCH

Severity

ERROR

Formal Specification
  • Let typeNames be the set of all output type names from all source schemas.
  • For each typeName in typeNames
    • Let types be the set of all types with the name typeName from all source schemas.
    • Let fieldNames be the set of all field names from all types in types.
    • For each fieldName in fieldNames
      • Let fields be the set of all fields with the name fieldName from all types in types.
      • Let externalFields be the set of all fields in fields that are marked with @external.
      • If externalFields is not empty
        • Let argumentNames be the set of all argument names from all fields in fields.
        • For each argumentName in argumentNames
          • Let arguments be the set of all arguments with the name argumentName from all fields in fields.
          • Let defaultValue be the first default value found in arguments.
          • Let externalArguments be the set of all arguments with the name argumentName from all fields in externalFields.
          • For each externalArgument in externalArguments
            • The default value of externalArgument must be equal to defaultValue.
Explanatory Text

This rule ensures that arguments on fields marked as @external have default values compatible with the corresponding arguments on fields from other source schemas where the field is defined (non-@external). Since @external fields represent fields that are resolved by other source schemas, their arguments and defaults must match to maintain consistent behavior across different source schemas.

Here, the name field on Product is defined in one source schema and marked as @external in another. The argument language has the same default value in both source schemas, satisfying the rule:

Example № 33# Subgraph A
type Product {
  name(language: String = "en"): String
}

# Subgraph B
type Product {
  name(language: String = "en") @external: String
}

Here, the name field on Product is defined in one source schema and marked as @external in another. The argument language has different default values in the two source schemas, violating the rule:

Counter Example № 34# Subgraph A
type Product {
  name(language: String = "en"): String
}

# Subgraph B
type Product {
  name(language: String = "de") @external: String
}

In the following counter example, the name field on Product is defined in one source schema and marked as @external in another. The argument language has a default value in the source schema where the field is defined, but it does not have a default value in the source schema where the field is marked as @external, violating the rule:

Counter Example № 35# Subgraph A
type Product {
  name(language: String = "en"): String
}

# Subgraph B
type Product {
  name(language: String): String @external
}

3.2.1.5External Argument Missing

Error Code

EXTERNAL_ARGUMENT_MISSING

Severity

ERROR

Formal Specification
  • Let typeNames be the set of all output type names from all source schemas.
  • For each typeName in typeNames
    • Let types be the set of all types with the name typeName from all source schemas.
    • Let fieldNames be the set of all field names from all types in types.
    • For each fieldName in fieldNames
      • Let fields be the set of all fields with the name fieldName from all types in types.
      • Let externalFields be the set of all fields in fields that are marked with @external.
      • Let nonExternalFields be the set of all fields in fields that are not marked with @external.
      • If externalFields is not empty
        • Let argumentNames be the set of all argument names from all fields in nonExternalFields
        • For each argumentName in argumentNames:
          • For each externalField in externalFields
            • argumentName must be present in the arguments of externalField.
Explanatory Text

This rule ensures that fields marked with @external have all the necessary arguments that exist on the corresponding field definitions in other source schemas. Each argument defined on the base field (the field definition in the source source schema) must be present on the @external field in other source schemas. If an argument is missing on an @external field, the field cannot be resolved correctly, which is an inconsistency.

In this example, the language argument is present on both the @external field in source schema B and the base field in source schema A, satisfying the rule:

Example № 36# Subgraph A
type Product {
  name(language: String): String
}

# Subgraph B
type Product {
  name(language: String): String @external
}

Here, the @external field in source schema B is missing the language argument that is present in the base field definition in source schema A, violating the rule:

Counter Example № 37# Subgraph A
type Product {
  name(language: String): String
}

# Subgraph B
type Product {
  name: String @external
}

3.2.1.6External Argument Type Mismatch

Error Code

EXTERNAL_ARGUMENT_TYPE_MISMATCH

Severity

ERROR

Formal Specification
  • Let typeNames be the set of all output type names from all source schemas.
  • For each typeName in typeNames
    • Let types be the set of all types with the name typeName from all source schemas.
    • Let fieldNames be the set of all field names from all types in types.
    • For each fieldName in fieldNames
      • Let fields be the set of all fields with the name fieldName from all types in types.
      • Let externalFields be the set of all fields in fields that are marked with @external.
      • Let nonExternalFields be the set of all fields in fields that are not marked with @external.
      • If externalFields is not empty
        • Let argumentNames be the set of all argument names from all fields in nonExternalFields
        • For each argumentName in argumentNames:
          • For each externalField in externalFields
            • Let externalArgument be the argument with the name argumentName from externalField.
            • externalArgument must strictly equal all arguments with the name argumentName from nonExternalFields.
Explanatory Text

This rule ensures that arguments on fields marked as @external have types compatible with the corresponding arguments on the fields defined in other source schemas. The arguments must have the exact same type signature, including nullability and list nesting.

Here, the @external field’s language argument has the same type (Language) as the base field, satisfying the rule:

Example № 38# Subgraph A
type Product {
  name(language: Language): String
}

# Subgraph B
type Product {
  name(language: Language): String
}

In this example, the @external field’s language argument type does not match the base field’s language argument type (Language vs. String), violating the rule:

Example № 39# Subgraph A
type Product {
  name(language: Language): String
}

# Subgraph B
type Product {
  name(language: String): String
}

3.2.1.7External Missing on Base

Error Code

EXTERNAL_MISSING_ON_BASE

Severity

ERROR

Formal Specification
  • Let typeNames be the set of all output type names from all source schemas.
  • For each typeName in typeNames
    • Let types be the set of all types with the name typeName from all source schemas.
    • Let fieldNames be the set of all field names from all types in types.
    • For each fieldName in fieldNames
      • Let fields be the set of all fields with the name fieldName from all types in types.
      • Let externalFields be the set of all fields in fields that are marked with @external.
      • Let nonExternalFields be the set of all fields in fields that are not marked with @external.
      • If externalFields is not empty
        • nonExternalFields must not be empty.
Explanatory Text

This rule ensures that any field marked as @external in a source schema is actually defined (non-@external) in at least one other source schema. The @external directive is used to indicate that the field is not usually resolved by the source schema it is declared in, implying it should be resolvable by at least one other source schema.

Here, the name field on Product is defined in source schema A and marked as @external in source schema B, which is valid because there is a base definition in source schema A:

Example № 40# Subgraph A
type Product {
  id: ID
  name: String
}

# Subgraph B
type Product {
  id: ID
  name: String @external
}

In this example, the name field on Product is marked as @external in source schema B but has no non-@external declaration in any other source schema, violating the rule:

Counter Example № 41# Subgraph A
type Product {
  id: ID
}

# Subgraph B
type Product {
  id: ID
  name: String @external
}

3.2.1.8External Type Mismatch

Error Code

EXTERNAL_TYPE_MISMATCH

Severity

ERROR

Formal Specification
  • Let typeNames be the set of all output type names from all source schemas.
  • For each typeName in typeNames
    • Let types be the set of all types with the name typeName from all source schemas.
    • Let fieldNames be the set of all field names from all types in types.
    • For each fieldName in fieldNames
      • Let fields be the set of all fields with the name fieldName from all types in types.
      • Let externalFields be the set of all fields in fields that are marked with @external.
      • Let nonExternalFields be the set of all fields in fields that are not marked with @external.
      • For each externalField in externalFields
        • The type of externalField must strictly equal all types of nonExternalFields.
Explanatory Text

This rule ensures that a field marked as @external has a return type compatible with the corresponding field defined in other source schemas. Fields with the same name must represent the same data type to maintain schema consistency

Here, the @external field name has the same return type (String) as the base field definition, satisfying the rule:

Example № 42# Subgraph A
type Product {
  name: String
}

# Subgraph B
type Product {
  name: String @external
}

In this example, the @external field name has a return type of ProductName that doesn’t match the base field’s return type String, violating the rule:

Counter Example № 43# Subgraph A
type Product {
  name: String
}

# Subgraph B
type Product {
  name: ProductName @external
}

3.2.1.9External Unused

Error Code

EXTERNAL_UNUSED

Severity

ERROR

Formal Specification
  • For each schema in all source schemas
    • Let types be the set of all composite types (object, interface) in schema.
    • For each type in types:
      • Let fields be the set of fields for type.
      • For each field in fields:
        • If field is marked with @external:
          • Let referencingFields be the set of fields in schema that reference type.
          • referencingFields must contain at least one field that references field in @provides
Explanatory Text

This rule ensures that every field marked as @external in a source schema is actually used by that source schema in a @provides directive.

Examples

In this example, the name field is marked with @external and is used by the @provides directive, satisfying the rule:

Example № 44# Subgraph A
type Product {
  id: ID
  name: String @external
}

type Query {
  productByName(name: String): Product @provides(fields: "name")
}

In this example, the name field is marked with @external but is not used by the @provides directive, violating the rule:

Counter Example № 45# Subgraph A
type Product {
  title: String @external
  author: Author
}

3.2.2Merge

3.2.3Post Merge Validation

3.2.3.1Empty Merged Object Type

Error Code

EMPTY_MERGED_OBJECT_TYPE

Severity ERROR

Formal Specification
  • Let types be the set of all object types across all source schemas
  • For each type in types:
IsObjectTypeEmpty(type)
  1. If type has @inaccessible directive
  2. return false
  3. Let fields be a set of all fields in type
  4. For each field in fields:
    1. If IsAccessible(field) is true
      1. return false
  5. return true
Explanatory Text

For object types defined across multiple source schemas, the merged object type is the superset of all fields defined in these source schemas. However, any field marked with @inaccessible in any source schema is hidden and not included in the merged object type. An object type with no fields, after considering @inaccessible annotations, is considered empty and invalid.

In the following example, the merged object type ObjectType1 is valid. It includes all fields from both source schemas, with field2 being hidden due to the @inaccessible directive in one of the source schemas:

type ObjectType1 {
  field1: String
  field2: Int @inaccessible
}

type ObjectType1 {
  field2: Int
  field3: Boolean
}

If the @inaccessible directive is applied to an object type itself, the entire merged object type is excluded from the composite execution schema, and it is not required to contain any fields.

type ObjectType1 @inaccessible {
  field1: String
  field2: Int
}

type ObjectType1 {
  field3: Boolean
}

This counter-example demonstrates an invalid merged object type. In this case, ObjectType1 is defined in two source schemas, but all fields are marked as @inaccessible in at least one of the source schemas, resulting in an empty merged object type:

Counter Example № 46type ObjectType1 {
  field1: String @inaccessible
  field2: Boolean
}

type ObjectType1 {
  field1: String
  field2: Boolean @inaccessible
}

3.3Validate Satisfiability

4Executor

A distributed GraphQL executor acts as an orchestrator that uses schema metadata to rewrite a GraphQL request into a query plan. This plan resolves the required data from subgraphs and coerces this data into the result of the GraphQL request.

4.1Configuration

The supergraph is a GraphQL IDL document that contains metadata for the query planner that describes the relationship between type system members and the type system members on subgraphs.

5Shared Types

In this section we outline directives and types that are shared between the subgraph configuration and the gateway configuration document.

5.1Name

scalar Name

The scalar Name represents a valid GraphQL type name.

5.2FieldSelection

scalar FieldSelection

The scalar FieldSelection represents a GraphQL field selection syntax.

Example № 47abc(def: 1) { ghi }

6Appendix A: Specification of FieldSelectionMap Scalar

6.1Introduction

This appendix focuses on the specification of the FieldSelectionMap scalar type. FieldSelectionMap is designed to express semantic equivalence between arguments of a field and fields within the result type. Specifically, it allows defining complex relationships between input arguments and fields in the output object by encapsulating these relationships within a parsable string format. It is used in the @is and @require directives.

To illustrate, consider a simple example from a GraphQL schema:

type Query {
  userById(userId: ID! @is(field: "id")): User! @lookup
}

In this schema, the userById query uses the @is directive with FieldSelectionMap to declare that the userId argument is semantically equivalent to the User.id field.

An example query might look like this:

query {
  userById(userId: "123") {
    id
  }
}

Here, it is expected that the userId “123” corresponds directly to User.id, resulting in the following response if correctly implemented:

{
  "data": {
    "userById": {
      "id": "123"
    }
  }
}

The FieldSelectionMap scalar is represented as a string that, when parsed, produces a SelectedValue.

A SelectedValue must exactly match the shape of the argument value to be considered valid. For non-scalar arguments, you must specify each field of the input type in SelectedObjectValue.

Example № 48extend type Query {
  findUserByName(user: UserInput! @is(field: "{ firstName: firstName }")): User
    @lookup
}
Counter Example № 49extend type Query {
  findUserByName(user: UserInput! @is(field: "firstName")): User @lookup
}

6.1.1Scope

The FieldSelectionMap scalar type is used to establish semantic equivalence between an argument and fields within a specific output type. This output type is always a composite type, but the way it’s determined can vary depending on the directive and context in which the FieldSelectionMap is used.

For example, when used with the @is directive, the FieldSelectionMap maps between the argument and fields in the return type of the field. However, when used with the @require directive, it maps between the argument and fields in the object type on which the field is defined.

Consider this example:

type Product {
  id: ID!
  delivery(
    zip: String!
    size: Int! @require(field: "dimension.size")
    weight: Int! @require(field: "dimension.weight")
  ): DeliveryEstimates
}

In this case, "dimension.size" and "dimension.weight" refer to fields of the Product type, not the DeliveryEstimates return type.

Consequently, a FieldSelectionMap must be interpreted in the context of a specific argument, its associated directive, and the relevant output type as determined by that directive’s behavior.

Examples

Scalar fields can be mapped directly to arguments.

This example maps the Product.weight field to the weight argument:

Example № 50type Product {
  shippingCost(weight: Float @require(field: "weight")): Currency
}

This example maps the Product.shippingWeight field to the weight argument:

Example № 51type Product {
  shippingCost(weight: Float @require(field: "shippingWeight")): Currency
}

Nested fields can be mapped to arguments by specifying the path. This example maps the nested field Product.packaging.weight to the weight argument:

Example № 52type Product {
  shippingCost(weight: Float @require(field: "packaging.weight")): Currency
}

Complex objects can be mapped to arguments by specifying each field.

This example maps the Product.width and Product.height fields to the dimension argument:

Example № 53type Product {
  shippingCost(
    dimension: DimensionInput @require(field: "{ width: width height: height }")
  ): Currency
}

The shorthand equivalent is:

Example № 54type Product {
  shippingCost(
    dimension: DimensionInput @require(field: "{ width height }")
  ): Currency
}

In case the input field names do not match the output field names, explicit mapping is required.

Example № 55type Product {
  shippingCost(
    dimension: DimensionInput @require(field: "{ w: width h: height }")
  ): Currency
}

Even if Product.dimension has all the fields needed for the input object, an explicit mapping is always required.

This example is NOT allowed because it lacks explicit mapping:

Counter Example № 56type Product {
  shippingCost(dimension: DimensionInput @require(field: "dimension")): Currency
}

Instead, you can traverse into output fields by specifying the path.

This example shows how to map nested fields explicitly:

Example № 57type Product {
  shippingCost(
    dimension: DimensionInput
      @require(field: "{ width: dimension.width height: dimension.height }")
  ): Currency
}

The path does NOT affect the structure of the input object. It is only used to traverse the output object:

Example № 58type Product {
  shippingCost(
    dimension: DimensionInput
      @require(field: "{ width: size.width height: size.height }")
  ): Currency
}

To avoid repeating yourself, you can prefix the selection with a path that ends in a dot to traverse INTO the output type.

This affects how fields get interpreted but does NOT affect the structure of the input object:

Example № 59type Product {
  shippingCost(
    dimension: DimensionInput @require(field: "dimension.{ width height }")
  ): Currency
}

This example is equivalent to the previous one:

Example № 60type Product {
  shippingCost(
    dimension: DimensionInput @require(field: "size.{ width height }")
  ): Currency
}

The path syntax is required for lists because list-valued path expressions would be ambiguous otherwise.

This example is NOT allowed because it lacks the dot syntax for lists:

Counter Example № 61type Product {
  shippingCost(
    dimensions: [DimensionInput]
      @require(field: "{ width: dimensions.width height: dimensions.height }")
  ): Currency
}

Instead, use the path syntax and brackets to specify the list elements:

Example № 62type Product {
  shippingCost(
    dimensions: [DimensionInput] @require(field: "dimensions[{ width height }]")
  ): Currency
}

With the path syntax it is possible to also select fields from a list of nested objects:

Example № 63type Product {
    shippingCost(partIds: @require(field: "parts[id]")): Currency
}

For more complex input objects, all these constructs can be nested. This allows for detailed and precise mappings.

This example nests the weight field and the dimension object with its width and height fields:

Example № 64type Product {
  shippingCost(
    package: PackageInput
      @require(field: "{ weight, dimension: dimension.{ width height } }")
  ): Currency
}

This example nests the weight field and the size object with its width and height fields:

Example № 65type Product {
  shippingCost(
    package: PackageInput
      @require(field: "{ weight, size: dimension.{ width height } }")
  ): Currency
}

The label can be used to nest values that aren’t nested in the output.

This example nests Product.width and Product.height under dimension:

Example № 66type Product {
  shippingCost(
    package: PackageInput
      @require(field: "{ weight, dimension: { width height } }")
  ): Currency
}

In the following example, dimensions are nested under dimension in the output:

Example № 67type Product {
  shippingCost(
    package: PackageInput
      @require(field: "{ weight, dimension: dimension.{ width height } }")
  ): Currency
}

6.2Language

According to the GraphQL specification, an argument is a key-value pair in which the key is the name of the argument and the value is a Value.

The Value of an argument can take various forms: it might be a scalar value (such as Int, Float, String, Boolean, Null, or Enum), a list (ListValue), an input object (ObjectValue), or a Variable.

Within the scope of the FieldSelectionMap, the relationship between input and output is established by defining the Value of the argument as a selection of fields from the output object.

Yet only certain types of Value have a semantic meaning. ObjectValue and ListValue are used to define the structure of the value. Scalar values, on the other hand, do not carry semantic importance in this context.

While variables may have legitimate use cases, they are considered out of scope for the current discussion.

However, it’s worth noting that there could be potential applications for allowing them in the future.

Given that these potential values do not align with the standard literals defined in the GraphQL specification, a new literal called SelectedValue is introduced, along with SelectedObjectValue.

Beyond these literals, an additional literal called Path is necessary.

6.2.1Name

Is equivalent to the Name defined in the GraphQL specification

6.2.2Path

FieldName
Name
TypeName
Name

The Path literal is a string used to select a single output value from the return type by specifying a path to that value. This path is defined as a sequence of field names, each separated by a period (.) to create segments.

Example № 68book.title

Each segment specifies a field in the context of the parent, with the root segment referencing a field in the return type of the query. Arguments are not allowed in a Path.

To select a field when dealing with abstract types, the segment selecting the parent field must specify the concrete type of the field using angle brackets after the field name if the field is not defined on an interface.

In the following example, the path mediaById<Book>.isbn specifies that mediaById returns a Book, and the isbn field is selected from that Book.

Example № 69mediaById<Book>.isbn

6.2.3SelectedValue

A SelectedValue is defined as either a Path or a SelectedObjectValue

A Path is designed to point to only a single value, although it may reference multiple fields depending on the return type. To allow selection from different paths based on type, a Path can include multiple paths separated by a pipe (|).

In the following example, the value could be title when referring to a Book and movieTitle when referring to a Movie.

Example № 70mediaById<Book>.title | mediaById<Movie>.movieTitle

The | operator can be used to match multiple possible SelectedValue. This operator is applied when mapping an abstract output type to a @oneOf input type.

Example № 71{ movieId: <Movie>.id } | { productId: <Product>.id }
Example № 72{ nested: { movieId: <Movie>.id } | { productId: <Product>.id }}

6.2.4SelectedObjectValue

SelectedObjectValue are unordered lists of keyed input values wrapped in curly-braces {}. It has to be used when the expected input type is an object type.

This structure is similar to the ObjectValue defined in the GraphQL specification, but it differs by allowing the inclusion of Path values within a SelectedValue, thus extending the traditional ObjectValue capabilities to support direct path selections.

A SelectedObjectValue following a Path is scoped to the type of the field selected by the Path. This means that the root of all SelectedValue inside the selection is no longer scoped to the root (defined by @is or @require) but to the field selected by the Path. The Path does not affect the structure of the input type.

This allows for reducing repetition in the selection.

The following example is valid:

Example № 73type Product {
  dimension: Dimension!
  shippingCost(
    dimension: DimensionInput! @require(field: "dimension.{ size weight }")
  ): Int!
}

The following example is equivalent to the previous one:

Example № 74type Product {
  dimensions: Dimension!
  shippingCost(
    dimensions: DimensionInput!
      @require(field: "{ size: dimensions.size weight: dimensions.weight }")
  ): Int! @lookup
}

6.2.5SelectedListValue

A SelectedListValue is an ordered list of SelectedValue wrapped in square brackets []. It is used to express semantic equivalence between an argument expecting a list of values and the values of a list field within the output object.

The SelectedListValue differs from the ListValue defined in the GraphQL specification by only allowing one SelectedValue as an element.

The following example is valid:

Example № 75type Product {
  parts: [Part!]!
  partIds(partIds: [ID!]! @require(field: "parts[id]")): [ID!]!
}

In this example, the partIds argument is semantically equivalent to the id fields of the parts list.

The following example is invalid because it uses multiple SelectedValue as elements:

Counter Example № 76type Product {
  parts: [Part!]!
  partIds(parts: [PartInput!]! @require(field: "parts[id name]")): [ID!]!
}

input PartInput {
  id: ID!
  name: String!
}

A SelectedObjectValue can be used as an element of a SelectedListValue to select multiple object fields as long as the input type is a list of structurally equivalent objects.

Similar to SelectedObjectValue, a SelectedListValue following a Path is scoped to the type of the field selected by the Path. This means that the root of all SelectedValue inside the selection is no longer scoped to the root (defined by @is or @require) but to the field selected by the Path. The Path does not affect the structure of the input type.

The following example is valid:

Example № 77type Product {
  parts: [Part!]!
  partIds(parts: [PartInput!]! @require(field: "parts[{ id name }]")): [ID!]!
}

input PartInput {
  id: ID!
  name: String!
}

In case the input type is a nested list, the shape of the input object must match the shape of the output object.

Example № 78type Product {
  parts: [[Part!]]!
  partIds(
    parts: [[PartInput!]]! @require(field: "parts[[{ id name }]]")
  ): [ID!]!
}

input PartInput {
  id: ID!
  name: String!
}

The following example is valid:

Example № 79type Query {
  findLocation(
    location: LocationInput!
      @is(field: "{ coordinates: coordinates[{lat: x lon: y}]}")
  ): Location @lookup
}

type Coordinate {
  x: Int!
  y: Int!
}

type Location {
  coordinates: [Coordinate!]!
}

input PositionInput {
  lat: Int!
  lon: Int!
}

input LocationInput {
  coordinates: [PositionInput!]!
}

6.3Validation

Validation ensures that FieldSelectionMap scalars are semantically correct within the given context.

Validation of FieldSelectionMap scalars occurs during the composition phase, ensuring that all FieldSelectionMap entries are syntactically correct and semantically meaningful relative to the context.

Composition is only possible if the FieldSelectionMap is validated successfully. An invalid FieldSelectionMap results in undefined behavior, making composition impossible.

In this section, we will assume the following type system in order to demonstrate examples:

type Query {
  mediaById(mediaId: ID!): Media
  findMedia(input: FindMediaInput): Media
  searchStore(search: SearchStoreInput): [Store]!
  storeById(id: ID!): Store
}

type Store {
  id: ID!
  city: String!
  media: [Media!]!
}

interface Media {
  id: ID!
}

type Book implements Media {
  id: ID!
  title: String!
  isbn: String!
  author: Author!
}

type Movie implements Media {
  id: ID!
  movieTitle: String!
  releaseDate: String!
}

type Author {
  id: ID!
  books: [Book!]!
}

input FindMediaInput @oneOf {
  bookId: ID
  movieId: ID
}

type SearchStoreInput {
  city: String
  hasInStock: FindMediaInput
}

6.3.1Path Field Selections

Each segment of a Path must correspond to a valid field defined on the current type context.

Formal Specification
  • For each segment in the Path:
    • If the segment is a field
      • Let fieldName be the field name in the current segment.
      • fieldName must be defined on the current type in scope.
Explanatory Text

The Path literal is used to reference a specific output field from a input field. Each segment in the Path must correspond to a field that is valid within the current type scope.

For example, the following Path is valid in the context of Book:

Example № 80title
Example № 81<Book>.title

Incorrect paths where the field does not exist on the specified type is not valid result in validation errors. For instance, if <Book>.movieId is referenced but movieId is not a field of Book, will result in an invalid Path.

Counter Example № 82movieId
Counter Example № 83<Book>.movieId

6.3.2Path Terminal Field Selections

Each terminal segment of a Path must follow the rules regarding whether the selected field is a leaf node.

Formal Specification
  • For each segment in the Path:
    • Let selectedType be the unwrapped type of the current segment.
    • If selectedType is a scalar or enum:
      • There must not be any further segments in Path.
    • If selectedType is an object, interface, or union:
      • There must be another segment in Path.
Explanatory Text

A Path that refers to scalar or enum fields must end at those fields. No further field selections are allowed after a scalar or enum. On the other hand, fields returning objects, interfaces, or unions must continue to specify further selections until you reach a scalar or enum field.

For example, the following Path is valid if title is a scalar field on the Book type:

Example № 84book.title

The following Path is invalid because title should not have subselections:

Counter Example № 85book.title.something

For non-leaf fields, the Path must continue to specify subselections until a leaf field is reached:

Example № 86book.author.id

Invalid Path where non-leaf fields do not have further selections:

Counter Example № 87book.author

6.3.3Type Reference Is Possible

Each segment of a Path that references a type, must be a type that is valid in the current context.

Formal Specification
  • For each segment in a Path:
    • If segment is a type reference:
      • Let type be the type referenced in the segment.
      • Let parentType be the type of the parent of the segment.
      • Let applicableTypes be the intersection of GetPossibleTypes(type) and GetPossibleTypes(parentType).
      • applicableTypes must not be empty.
GetPossibleTypes(type)
  1. If type is an object type, return a set containing type.
  2. If type is an interface type, return the set of types implementing type.
  3. If type is a union type, return the set of possible types of type.
Explanatory Text

Type references inside a Path must be valid within the context of the surrounding type. A type reference is only valid if the referenced type could logically apply within the parent type.

6.3.4Values of Correct Type

Formal Specification
  • For each SelectedValue value:
    • Let type be the type expected in the position value is found.
    • value must be coercible to type.
Explanatory Text

Literal values must be compatible with the type expected in the position they are found.

The following examples are valid use of value literals in the context of FieldSelectionMap scalar:

Example № 88type Query {
  storeById(id: ID! @is(field: "id")): Store! @lookup
}

type Store {
  id: ID
  city: String!
}

Non-coercible values are invalid. The following examples are invalid:

Counter Example № 89type Query {
  storeById(id: ID! @is(field: "id")): Store! @lookup
}

type Store {
  id: Int
  city: String!
}

6.3.5Selected Object Field Names

Formal Specification
  • For each Selected Object Field field in the document:
    • Let fieldName be the Name of field.
    • Let fieldDefinition be the field definition provided by the parent selected object type named fieldName.
    • fieldDefinition must exist.
Explanatory Text

Every field provided in an selected object value must be defined in the set of possible fields of that input object’s expected type.

For example, the following is valid:

Example № 90type Query {
  storeById(id: ID! @is(field: "id")): Store! @lookup
}

type Store {
  id: ID
  city: String!
}

In contrast, the following is invalid because it uses a field “address” which is not defined on the expected type:

Counter Example № 91extend type Query {
  storeById(id: ID! @is(field: "address")): Store! @lookup
}

type Store {
  id: ID
  city: String!
}

6.3.6Selected Object Field Uniqueness

Formal Specification
  • For each selected object value selectedObject:
    • For every field in selectedObject:
      • Let name be the Name of field.
      • Let fields be all Selected Object Fields named name in selectedObject.
      • fields must be the set containing only field.
Explanatory Text

Selected objects must not contain more than one field with the same name, as it would create ambiguity and potential conflicts.

For example, the following is invalid:

Counter Example № 92extend type Query {
  storeById(id: ID! @is(field: "id id")): Store! @lookup
}

type Store {
  id: ID
  city: String!
}

6.3.7Required Selected Object Fields

Formal Specification
  • For each Selected Object:
    • Let fields be the fields provided by that Selected Object.
    • Let fieldDefinitions be the set of input object field definitions of that Selected Object.
    • For each fieldDefinition in fieldDefinitions:
      • Let type be the expected type of fieldDefinition.
      • Let defaultValue be the default value of fieldDefinition.
      • If type is Non-Null and defaultValue does not exist:
        • Let fieldName be the name of fieldDefinition.
        • Let field be the input object field in fields named fieldName.
        • field must exist.
Explanatory Text

Input object fields may be required. This means that a selected object field is required if the corresponding input field is required. Otherwise, the selected object field is optional.

For instance, if the UserInput type requires the id field:

Example № 93input UserInput {
  id: ID!
  name: String!
}

Then, an invalid selection would be missing the required id field:

Counter Example № 94extend type Query {
  userById(user: UserInput! @is(field: "{ name: name }")): User! @lookup
}

If the UserInput type requires the name field, but the User type has an optional name field, the following selection would be valid.

Example № 95extend type Query {
  findUser(input: UserInput! @is(field: "{ name: name }")): User! @lookup
}

type User {
  id: ID
  name: String
}

input UserInput {
  id: ID
  name: String!
}

But if the UserInput type requires the name field but it’s not defined in the User type, the selection would be invalid.

Counter Example № 96extend type Query {
  findUser(input: UserInput! @is(field: "{ id: id }")): User! @lookup
}

type User {
  id: ID
}

input UserInput {
  id: ID
  name: String!
}

§Index

  1. FieldName
  2. FieldsAreMergeable
  3. GetPossibleTypes
  4. IsObjectTypeEmpty
  5. Path
  6. PathSegment
  7. SelectedListValue
  8. SelectedObjectField
  9. SelectedObjectValue
  10. SelectedValue
  11. TypeName
  12. ValidateArgumentDefaultValues
  13. ValidateDefaultValue
  14. ValidateInputFieldDefaultValues
  1. 1Overview
  2. 2Source Schema
    1. 2.1Directives
      1. 2.1.1@lookup
      2. 2.1.2@internal
      3. 2.1.3@is
      4. 2.1.4@require
      5. 2.1.5@key
      6. 2.1.6@shareable
      7. 2.1.7@provides
      8. 2.1.8@external
      9. 2.1.9@override
  3. 3Schema Composition
    1. 3.1Validate Source Schemas
    2. 3.2Merge Source Schemas
      1. 3.2.1Pre Merge Validation
        1. 3.2.1.1Enum Type Default Value Uses Inaccessible Value
        2. 3.2.1.2Output Field Types Mergeable
        3. 3.2.1.3Disallowed Inaccessible Elements
        4. 3.2.1.4External Argument Default Mismatch
        5. 3.2.1.5External Argument Missing
        6. 3.2.1.6External Argument Type Mismatch
        7. 3.2.1.7External Missing on Base
        8. 3.2.1.8External Type Mismatch
        9. 3.2.1.9External Unused
      2. 3.2.2Merge
      3. 3.2.3Post Merge Validation
        1. 3.2.3.1Empty Merged Object Type
    3. 3.3Validate Satisfiability
  4. 4Executor
    1. 4.1Configuration
  5. 5Shared Types
    1. 5.1Name
    2. 5.2FieldSelection
  6. 6Appendix A: Specification of FieldSelectionMap Scalar
    1. 6.1Introduction
      1. 6.1.1Scope
    2. 6.2Language
      1. 6.2.1Name
      2. 6.2.2Path
      3. 6.2.3SelectedValue
      4. 6.2.4SelectedObjectValue
      5. 6.2.5SelectedListValue
    3. 6.3Validation
      1. 6.3.1Path Field Selections
      2. 6.3.2Path Terminal Field Selections
      3. 6.3.3Type Reference Is Possible
      4. 6.3.4Values of Correct Type
      5. 6.3.5Selected Object Field Names
      6. 6.3.6Selected Object Field Uniqueness
      7. 6.3.7Required Selected Object Fields
  7. §Index