Composition Hints

Reference for composition hints


When you successfully compose your subgraph schemas into a supergraph schema, the composition process can flag potential improvements or hints. Hints are violations of the GraphOS schema linter's composition rules . You can review them on the Checks page in GraphOS Studio or via the Rover CLI .

 note
Composition hints only appear in GraphOS Studio and via the rover subgraph check command for graphs on federation version 2.4 or later. You can update a graph's version from its Settings page in GraphOS Studio .

The rover subgraph check command outputs rule violations with the severity levels you've configured for your graph variant. The rover supergraph compose command outputs rule violations for all local subgraph schemas.

See below for a list of composition rules categorized by rule type. The heading for each rule is the code that GraphOS returns for the rule violation. Refer to the rules reference page for a comprehensive list of linter rules.

Inconsistent elements

These rules identity inconsistencies in fields, types, arguments, etc across subgraphs. Such inconsistencies can disrupt or even break composition .

Compatibility

In some cases, inconsistency rules also indicate the compatibility of checked types. Two types are compatible if one is a non-nullable version, a list version, a subtype, or a combination of any of these of the other.

For example, the price fields in the example subgraphs below are inconsistent and incompatible because they use completely different types (Float vs String):

GraphQL
Subgraph
type Product {
  id: ID!
  name: String
  price: Float #highlight-line
}
GraphQL
Subgraph
type Product {
  id: ID!
  name: String
  price: String #highlight-line
}

These price fields in the example subgraphs below are inconsistent but compatible since both use Floats, but one is nullable and the other is the non-nullable list of Floats.

GraphQL
Subgraph
type Product {
  id: ID!
  name: String
  price: Float #highlight-line
}
GraphQL
Subgraph
type Product {
  id: ID!
  name: String
  price: [Float]! #highlight-line
}
INCONSISTENT_ARGUMENT_PRESENCE

What it does

Checks that an argument of a field or directive definition is present in all subgraphs.

Rationale

The supergraph schema only includes arguments that are exactly the same for all subgraphs that define its field or directive. Learn more.

Examples

The following example violates the rule:
GraphQL
type Product {
  id: ID!
  name: String
  price(currency: Currency): Float #highlight-line
}
GraphQL
type Product {
  id: ID!
  name: String
  price(currency: Currency, taxIncluded: Boolean): Float #highlight-line
}

Use instead:
GraphQL
type Product {
  id: ID!
  name: String
  price(currency: Currency, taxIncluded: Boolean): Float #highlight-line
}
GraphQL
type Product {
  id: ID!
  name: String
  price(currency: Currency, taxIncluded: Boolean): Float #highlight-line
}
INCONSISTENT_BUT_COMPATIBLE_ARGUMENT_TYPE

What it does

Checks that arguments (of a field, input field, or directive definition) have the exact same types in all subgraphs. This warning/error indicates the argument types are compatible but inconsistent.

Rationale

The supergraph schema only includes arguments that are exactly the same for all subgraphs that define its field or directive. Learn more.

Examples


Because subgraph A's price field expects a non-nullable Currency argument type and subgraph B allows a nullable Currency argument type, the following example violates the rule:

GraphQL
type Product {
  id: ID!
  name: String
  price(currency: Currency!): Float #highlight-line
}

enum Currency {
  USD
  EUR
  GBP
  JPY
  AUD
  CAD
}
GraphQL
type Product {
  id: ID!
  name: String
  price(currency: Currency): Float #highlight-line
}

enum Currency {
  USD
  EUR
  GBP
  JPY
  AUD
  CAD
}

Use instead:
GraphQL
type Product {
  id: ID!
  name: String
  price(currency: Currency!): Float #highlight-line
}

enum Currency {
  USD
  EUR
  GBP
  JPY
  AUD
  CAD
}
GraphQL
type Product {
  id: ID!
  name: String
  price(currency: Currency!): Float #highlight-line
}

enum Currency {
  USD
  EUR
  GBP
  JPY
  AUD
  CAD
}
INCONSISTENT_BUT_COMPATIBLE_FIELD_TYPE

What it does

Checks that fields have the exact same types in all subgraphs. This warning/error indicates the field types are compatible but inconsistent.

Rationale

Inconsistent types can lead to discrepancies in the way data is retrieved and processed, resulting in unexpected client behavior.

Examples


The following example violates the rule:
GraphQL
type Product {
  id: ID!
  name: String
  price: Money #highlight-line
}

type Money {
  amount: Float!
  currency: Currency!
}

enum Currency {
  USD
  EUR
  GBP
  JPY
  AUD
  CAD
}
GraphQL
type Product {
  id: ID!
  name: String
  price: Money! #highlight-line
}

type Money {
  amount: Float!
  currency: Currency!
}

enum Currency {
  USD
  EUR
  GBP
  JPY
  AUD
  CAD
}

Use instead:
GraphQL
type Product {
  id: ID!
  name: String
  price: Money! #highlight-line
}

type Money {
  amount: Float!
  currency: Currency!
}

enum Currency {
  USD
  EUR
  GBP
  JPY
  AUD
  CAD
}
GraphQL
type Product {
  id: ID!
  name: String
  price: Money! #highlight-line
}

type Money {
  amount: Float!
  currency: Currency!
}

enum Currency {
  USD
  EUR
  GBP
  JPY
  AUD
  CAD
}
INCONSISTENT_DEFAULT_VALUE_PRESENCE

What it does

Checks that argument definitions (of a field, input field, or directive definition) consistently include—or consistently don't include—a default value in all subgraphs that define the argument.

Rationale

Inconsistent defaults can lead to discrepancies in the way data is retrieved and processed, resulting in unexpected client behavior.

Examples

The following example violates the rule:
GraphQL
type Product {
  id: ID!
  name: String
  weight(kg: Float = 1.0): Float #highlight-line
}
GraphQL
type Product {
  id: ID!
  name: String
  weight(kg: Float): Float #highlight-line
}

Use instead:
GraphQL
type Product {
  id: ID!
  name: String
  weight(kg: Float = 1.0): Float #highlight-line
}
GraphQL
type Product {
  id: ID!
  name: String
  weight(kg: Float = 1.0): Float #highlight-line
}
INCONSISTENT_DESCRIPTION

What it does

Checks that a type's description is consistent across subgraphs.

Rationale

Inconsistent type descriptions can lead to inconsistent expectations around type values resulting in unexpected client behavior.

Examples

The following example violates the rule:
GraphQL
"""
A type representing a product.
"""
type Product {
  id: ID!
  name: String
}
GraphQL
"""
An object representing a product.
"""
type Product {
  id: ID!
  name: String
}

Use instead:
GraphQL
"""
A type representing a product.
"""
type Product {
  id: ID!
  name: String
}
GraphQL
"""
A type representing a product.
"""
type Product {
  id: ID!
  name: String
}
INCONSISTENT_ENTITY

What it does

Checks that an object is consistently declared as an entity (has a @key) in all subgraphs in which the object is defined.

Rationale

If an object is only declared as an entity in some subgraphs, the federated schema won't have complete information about that entity.

Examples

The following example violates the rule:
GraphQL
type Product
  @key(fields: "id") { #highlight-line
  id: ID!
  name: String
}
GraphQL
type Product { #highlight-line
  id: ID!
  stock: Int
}

Use instead:
GraphQL
type Product
  @key(fields: "id") { #highlight-line
  id: ID!
  name: String
}
GraphQL
type Product
  @key(fields: "id") { #highlight-line
  id: ID!
  stock: Int
}
INCONSISTENT_ENUM_VALUE_FOR_INPUT_ENUM

What it does

Checks that values of an input enum type are consistently defined in all subgraphs that declare the enum.

Rationale

When a value of an enum that is only used as an input type is defined in only some of the subgraphs that declare the enum, inconsistent values won't be merged into the supergraph. Learn more.

Examples

The following example violates the rule:
GraphQL
enum ProductStatus {
  AVAILABLE
  SOLD_OUT
  BACK_ORDER #highlight-line
}

input ProductInput {
  name: String!
  status: ProductStatus!
}
GraphQL
enum ProductStatus {
  AVAILABLE
  SOLD_OUT
}

input ProductInput {
  name: String!
  status: ProductStatus!
}

Use instead:
GraphQL
enum ProductStatus {
  AVAILABLE
  SOLD_OUT
  BACK_ORDER
}

input ProductInput {
  name: String!
  status: ProductStatus!
}
GraphQL
enum ProductStatus {
  AVAILABLE
  SOLD_OUT
  BACK_ORDER
}

input ProductInput {
  name: String!
  status: ProductStatus!
}
INCONSISTENT_ENUM_VALUE_FOR_OUTPUT_ENUM

What it does

Checks that values of an output enum type are consistently defined in all subgraphs that declare the enum.

Rationale

When values of an output or unused enum type definition are inconsistent, all values are merged into the supergraph. Regardless, it can be helpful to set expectations by including all possible values in all subgraphs defining the enum. Learn more.

Examples

The following example violates the rule:
GraphQL
enum OrderStatus {
  CREATED
  PROCESSING #highlight-line
  COMPLETED
}

type Order {
  name: String!
  status: OrderStatus!
}
GraphQL
enum OrderStatus {
  CREATED
  COMPLETED
}

type Order {
  name: String!
  status: OrderStatus!
}

Use instead:
GraphQL
enum OrderStatus {
  CREATED
  PROCESSING
  COMPLETED
}

type Order {
  name: String!
  status: OrderStatus!
}
GraphQL
enum OrderStatus {
  CREATED
  PROCESSING
  COMPLETED
}

type Order {
  name: String!
  status: OrderStatus!
}
INCONSISTENT_EXECUTABLE_DIRECTIVE_LOCATIONS

What it does

Checks that an executable directive definition is declared with consistent locations across all subgraphs.

Rationale

An executable directive is composed into the supergraph schema only when it is defined identically in all subgraphs. Learn more.

Examples

The following example violates the rule:
GraphQL
directive @log(message: String!) on QUERY #highlight-line
GraphQL
directive @log(message: String!) on FIELD #highlight-line

Use instead:
GraphQL
directive @log(message: String!) on QUERY | FIELD
GraphQL
directive @log(message: String!) on QUERY | FIELD
INCONSISTENT_EXECUTABLE_DIRECTIVE_PRESENCE

What it does

Checks that an executable directive definition is declared in all subgraphs.

Rationale

An executable directive is composed into the supergraph schema only if it's defined in all subgraphs. Learn more.

Examples

The following example violates the rule:
GraphQL
directive @modify(field: String!) on FIELD
GraphQL
# 🦗🦗🦗

Use instead:
GraphQL
directive @modify(field: String!) on FIELD
GraphQL
directive @modify(field: String!) on FIELD
INCONSISTENT_EXECUTABLE_DIRECTIVE_REPEATABLE

What it does

Checks that an executable directive definition is marked repeatable in all subgraphs that define it.

Rationale

Unless an executable directive is defined as repeatable in all subgraphs, it won't be repeatable in the supergraph.

Examples

The following example violates the rule:
GraphQL
directive @validateLength(max: Int!) repeatable on FIELD
GraphQL
directive @validateLength(max: Int!) on FIELD

Use instead:
GraphQL
directive @validateLength(max: Int!) repeatable on FIELD
GraphQL
directive @validateLength(max: Int!) repeatable on FIELD
INCONSISTENT_INPUT_OBJECT_FIELD

What it does

Checks that a field of an input object definition is defined in all the subgraphs that declare the input object.

Rationale

The supergraph schema includes only the input object fields that all subgraphs define for the object. Learn more.

Examples

The following example violates the rule:
GraphQL
input ProductInput {
  name: String
  price: Float #highlight-line
}

input OrderInput {
  product: ProductInput
}
GraphQL
input ProductInput {
  name: String
}

input OrderInput {
  product: ProductInput
}

Use instead:
GraphQL
input ProductInput {
  name: String
  price: Float
}

input OrderInput {
  product: ProductInput
}
GraphQL
input ProductInput {
  name: String
  price: Float
}

input OrderInput {
  product: ProductInput
}
INCONSISTENT_INTERFACE_VALUE_TYPE_FIELD

What it does

Checks that a field of an interface value type (has no @key in any subgraph) is defined in all the subgraphs that declare the type.

Rationale

If different subgraphs contribute different fields to an interface type, any object types that implement that interface must define all contributed fields from all subgraphs. Otherwise, composition fails. Learn more.

Examples

The following example violates the rule:
GraphQL
interface Product {
  id: ID!
  name: String
  cost: Float #highlight-line
}

type DigitalProduct implements Product {
  id: ID!
  name: String
  cost: Float
  size: Int
}
GraphQL
interface Product {
  id: ID!
  name: String
  # cost is not defined in the interface
}

type PhysicalProduct implements Product {
  id: ID!
  name: String
  cost: Float
  weight: Float
}

Use instead:
GraphQL
interface Product {
  id: ID!
  name: String
  cost: Float #highlight-line
}

type DigitalProduct implements Product {
  id: ID!
  name: String
  cost: Float
  size: Int
}
GraphQL
interface Product {
  id: ID!
  name: String
  cost: Float #highlight-line
}

type PhysicalProduct implements Product {
  id: ID!
  name: String
  cost: Float
  weight: Float
}
INCONSISTENT_NON_REPEATABLE_DIRECTIVE_ARGUMENTS

What it does

Checks if a non-repeatable directive is applied to a schema element across different subgraphs with differing arguments.

Rationale

Inconsistent directive argument usage can lead to misunderstandings and potential issues in client applications.

Examples

The following example violates the rule:
GraphQL
type Product {
  id: ID!
  name: String
}

type Query {
  allProducts: [Product] @customDirective(orderBy: "name") #highlight-line
}
GraphQL
type Product {
  id: ID!
  name: String
}

type Query {
  allProducts: [Product] @customDirective(orderBy: "price") #highlight-line
}

Use instead:
GraphQL
type Product {
  id: ID!
  name: String
}

type Query {
  allProducts: [Product] @customDirective(orderBy: "name") #highlight-line
}
GraphQL
type Product {
  id: ID!
  name: String
}

type Query {
  allProducts: [Product] @customDirective(orderBy: "name") #highlight-line
}
INCONSISTENT_OBJECT_VALUE_TYPE_FIELD

What it does

Checks that object value types (has no @key in any subgraph) declare the same fields in all subgraphs that declare the type.

Rationale

When an object value type includes differing fields across subgraphs, the supergraph schema includes the union of all fields. Depending on which subgraph executes the query, omitted fields may be unresolvable . You can include the same types as shown below or check out Solutions for unresolvable fields .

Examples

The following example violates the rule:
GraphQL
type Product {
  id: ID! @shareable
  name: String @shareable
  price: Float #highlight-line
}
GraphQL
type Product {
  id: ID! @shareable
  name: String @shareable
}

Use instead:
GraphQL
type Product @shareable {
  id: ID!
  name: String
  price: Float #highlight-line
}
GraphQL
type Product @shareable {
  id: ID!
  name: String
  price: Float #highlight-line
}
INCONSISTENT_RUNTIME_TYPES_FOR_SHAREABLE_RETURN

What it does

Checks that a @shareable field returns consistent sets of runtime types in all subgraphs in which it's defined.

Rationale

Each subgraph's resolver for a @shareable field should behave identically. Otherwise, requests might return inconsistent results depending on which subgraph resolves the field. Learn more.

Examples

The following example violates the rule:
GraphQL
type Product {
  id: ID!
  name: String
  details: Details @shareable #highlight-line
}

type Details {
  size: String #highlight-line
}
GraphQL
type Product {
  id: ID!
  name: String
  details: Details @shareable #highlight-line
}

type Details {
  weight: Float #highlight-line
}

Use instead:
GraphQL
type Product {
  id: ID!
  name: String
  details: Details @shareable #highlight-line
}

type Details {
  size: String #highlight-line
}
GraphQL
type Product {
  id: ID!
  name: String
  details: Details @shareable #highlight-line
}

type Details {
  size: String #highlight-line
}
INCONSISTENT_TYPE_SYSTEM_DIRECTIVE_LOCATIONS

What it does

Checks that a type system directive definition is declared with consistent locations across subgraphs.

Rationale

To ensure consistent expectations, it's best that all definitions declare the same locations. Learn more.

Examples

The following example violates the rule:
GraphQL
directive @customDirective(message: String!) on OBJECT | FIELD_DEFINITION #highlight-line
GraphQL
directive @customDirective(message: String!) on FIELD_DEFINITION #highlight-line

Use instead:
GraphQL
directive @customDirective(message: String!) on OBJECT | FIELD_DEFINITION
GraphQL
directive @customDirective(message: String!) on OBJECT | FIELD_DEFINITION
INCONSISTENT_TYPE_SYSTEM_DIRECTIVE_REPEATABLE

What it does

Checks that a type system directive definition is marked repeatable in all subgraphs that declare the directive and will be repeatable in the supergraph.

Rationale

To ensure consistent expectations, directives should have consistent definitions across subgraphs, including whether they are repeatable. Learn more.

Examples

The following example violates the rule:
GraphQL
directive @customDirective on OBJECT
GraphQL
directive @customDirective repeatable on OBJECT

Use instead:
GraphQL
directive @customDirective repeatable on OBJECT
GraphQL
directive @customDirective repeatable on OBJECT
INCONSISTENT_UNION_MEMBER

What it does

Checks that a member of a union definition is defined in all subgraphs that declare the union.

Rationale

When a union definition has inconsistent members, the supergraph schema includes all members in the union definition. Nevertheless, to ensure consistent expectations, it's best that all union definitions declare the same members across subgraphs. Learn more.

Examples

The following example violates the rule:
GraphQL
type Product {
  id: ID!
  name: String
}

type Service {
  id: ID!
  description: String
}

union SearchResult = Product | Service #highlight-line
GraphQL
type Product {
  id: ID!
  name: String
}

union SearchResult = Product #highlight-line

Use instead:
GraphQL
type Product {
  id: ID!
  name: String
}

type Service {
  id: ID!
  description: String
}

union SearchResult = Product | Service #highlight-line
GraphQL
type Product {
  id: ID!
  name: String
}

type Service {
  id: ID!
  description: String
}

union SearchResult = Product | Service #highlight-line

Overridden and unused elements

OVERRIDE_DIRECTIVE_CAN_BE_REMOVED

What it does

Checks that a field with the @override directive no longer exists in a source subgraph.

Rationale

If a field with the @override directive no longer exists in a source subgraph, the directive can be safely removed.

Examples

The following example violates the rule:
GraphQL
type Product @key(fields: "id") {
  id: ID!
  inStock: Boolean! @override(from: "Subgraph B")
}
GraphQL
type Product @key(fields: "id") {
  id: ID!
  name: String!
}

Use instead:
GraphQL
type Product @key(fields: "id") {
  id: ID!
  inStock: Boolean!
}
GraphQL
type Product @key(fields: "id") {
  id: ID!
  name: String!
}
OVERRIDDEN_FIELD_CAN_BE_REMOVED

What it does

Checks if a field has been overridden by another subgraph.

Rationale

You should consider removing overridden fields to avoid confusion.

Examples

The following example violates the rule:
GraphQL
type Product @key(fields: "id") {
  id: ID!
  inStock: Boolean! @override(from: "Subgraph B")
}
GraphQL
type Product @key(fields: "id") {
  id: ID!
  name: String!
  inStock: Boolean!
}

Use instead:
GraphQL
type Product @key(fields: "id") {
  id: ID!
  name: String!
  inStock: Boolean!
}
GraphQL
type Product @key(fields: "id") {
  id: ID!
  name: String!
}
OVERRIDE_MIGRATION_IN_PROGRESS

What it does

Checks if a field migration is in progress.

Rationale

You should complete a field migration.

Examples

The following example violates the rule:
GraphQL
type Product @key(fields: "id") {
  id: ID!
  inStock: Boolean! @override(from: "Subgraph B", label: "percent(50)")
}
GraphQL
type Product @key(fields: "id") {
  id: ID!
  name: String!
  inStock: Boolean!
}

After completing the migration, use instead:
GraphQL
type Product @key(fields: "id") {
  id: ID!
  name: String!
  inStock: Boolean!
}
GraphQL
type Product @key(fields: "id") {
  id: ID!
  name: String!
}
UNUSED_ENUM_TYPE

What it does

Checks if an enum type is defined but no field or argument in any subgraph references it.

Rationale

If the enum is defined, it should be used or removed.

Examples

The following example violates the rule:
GraphQL
enum ProductStatus {
  AVAILABLE
  SOLD_OUT
}

type Product {
  id: ID!
  name: String
}
GraphQL
type Order {
  id: ID!
  product: Product
  status: String
}

Use instead:
GraphQL
enum ProductStatus {
  AVAILABLE
  SOLD_OUT
}

type Product {
  id: ID!
  name: String
  status: ProductStatus
}
GraphQL
type Order {
  id: ID!
  product: Product
  status: ProductStatus
}

Directives

DIRECTIVE_COMPOSITION

What it does

Checks for issues when composing custom directives.

MERGED_NON_REPEATABLE_DIRECTIVE_ARGUMENTS

What it does

Checks if a non-repeatable directive has been applied to the same schema element in different subgraphs with different arguments. Learn more.

Rationale

Arguments should be consistent across a non-repeatable directive's usage. If arguments differ, it may be a sign that subgraph owners need to communicate about the directive's usage. If the arguments need to differ, consider using a repeatable directive.

Examples

The following example violates the rule:
GraphQL
type Product {
  id: ID!
  name: String
}

type Query {
  products: [Product] @customDirective(orderBy: ["name"]) #highlight-line
}
GraphQL
type Product {
  id: ID!
  name: String
}

type Query {
  products: [Product] @customDirective(orderBy: ["price"]) #highlight-line
}

Use instead:
GraphQL
type Product {
  id: ID!
  name: String
}

type Query {
  products: [Product] @customDirective(orderBy: ["name", "price"]) #highlight-line
}
GraphQL
type Product {
  id: ID!
  name: String
}

type Query {
  products: [Product] @customDirective(orderBy: ["name", "price"]) #highlight-line
}
NO_EXECUTABLE_DIRECTIVE_INTERSECTION

What it does

Checks for executable directive definitions with no shared locations across subgraphs.

Rationale

Directives must only be used in the locations they are declared to belong in. If the same executable directive is defined with different locations in different subgraphs, it may be a sign that subgraph owners need to communicate about the directive's usage.

Examples

The following example violates the rule:
GraphQL
directive @log(message: String!) on QUERY #highlight-line
GraphQL
directive @log(message: String!) on FIELD #highlight-line

Use instead:
GraphQL
directive @log(message: String!) on QUERY | FIELD
GraphQL
directive @log(message: String!) on QUERY | FIELD
FROM_SUBGRAPH_DOES_NOT_EXIST

What it does

Checks that the source subgraph specified by @override directive exists.

Rationale

The @override directive indicates that an object field is now resolved by a different subgraph. The directive can't work unless you specify an existing subgraph to resolve the field from.

Examples

The following example violates the rule:
GraphQL
type Product @key(fields: "id") {
  id: ID!
  inStock: Boolean! @override(from: "Subgraph B")
}
GraphQL
# Subgraph B doesn't exist

Use instead:
GraphQL
type Product @key(fields: "id") {
  id: ID!
  inStock: Boolean! @override(from: "Subgraph B")
}
GraphQL
type Product @key(fields: "id") {
  id: ID!
  inStock: Boolean!
}