When you successfully compose the schemas provided by your subgraphs into a supergraph schema, the composition process can flag potential improvements or hints. Hints are violations of the GraphOS schema linter'scomposition 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.
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.
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 pricefields in the example subgraphs below are inconsistent and incompatible because they use completely different types (Float vs String):
Subgraph A
typeProduct{
id:ID!
name:String
price:Float
}
Subgraph B
typeProduct{
id:ID!
name:String
price:String
}
These pricefields 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.
Subgraph A
typeProduct{
id:ID!
name:String
price:Float
}
Subgraph B
typeProduct{
id:ID!
name:String
price:[Float]!
}
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.
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 pricefield expects a non-nullable Currencyargument type and subgraph B allows a nullable Currencyargument type, the following example violates the rule:
GraphQL
❌ Subgraph A
typeProduct{
id:ID!
name:String
price(currency:Currency!):Float
}
enumCurrency{
USD
EUR
GBP
JPY
AUD
CAD
}
GraphQL
❌ Subgraph B
typeProduct{
id:ID!
name:String
price(currency:Currency):Float
}
enumCurrency{
USD
EUR
GBP
JPY
AUD
CAD
}
Use instead:
GraphQL
✅ Subgraph A
typeProduct{
id:ID!
name:String
price(currency:Currency!):Float
}
enumCurrency{
USD
EUR
GBP
JPY
AUD
CAD
}
GraphQL
✅ Subgraph B
typeProduct{
id:ID!
name:String
price(currency:Currency!):Float
}
enumCurrency{
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
❌ Subgraph A
typeProduct{
id:ID!
name:String
price:Money
}
typeMoney{
amount:Float!
currency:Currency!
}
enumCurrency{
USD
EUR
GBP
JPY
AUD
CAD
}
GraphQL
❌ Subgraph B
typeProduct{
id:ID!
name:String
price:Money!
}
typeMoney{
amount:Float!
currency:Currency!
}
enumCurrency{
USD
EUR
GBP
JPY
AUD
CAD
}
Use instead:
GraphQL
✅ Subgraph A
typeProduct{
id:ID!
name:String
price:Money!
}
typeMoney{
amount:Float!
currency:Currency!
}
enumCurrency{
USD
EUR
GBP
JPY
AUD
CAD
}
GraphQL
✅ Subgraph B
typeProduct{
id:ID!
name:String
price:Money!
}
typeMoney{
amount:Float!
currency:Currency!
}
enumCurrency{
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
❌ Subgraph A
typeProduct{
id:ID!
name:String
weight(kg:Float=1.0):Float
}
GraphQL
❌ Subgraph B
typeProduct{
id:ID!
name:String
weight(kg:Float):Float
}
Use instead:
GraphQL
✅ Subgraph A
typeProduct{
id:ID!
name:String
weight(kg:Float=1.0):Float
}
GraphQL
✅ Subgraph B
typeProduct{
id:ID!
name:String
weight(kg:Float=1.0):Float
}
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
❌ Subgraph A
"""
A type representing a product.
"""
typeProduct{
id:ID!
name:String
}
GraphQL
❌ Subgraph B
"""
An object representing a product.
"""
typeProduct{
id:ID!
name:String
}
Use instead:
GraphQL
✅ Subgraph A
"""
A type representing a product.
"""
typeProduct{
id:ID!
name:String
}
GraphQL
✅ Subgraph B
"""
A type representing a product.
"""
typeProduct{
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
❌ Subgraph A
typeProduct@key(fields:"id"){
id:ID!
name:String
}
GraphQL
❌ Subgraph B
typeProduct{
id:ID!
stock:Int
}
Use instead:
GraphQL
✅ Subgraph A
typeProduct@key(fields:"id"){
id:ID!
name:String
}
GraphQL
✅ Subgraph B
typeProduct@key(fields:"id"){
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
❌ Subgraph A
enumProductStatus{
AVAILABLE
SOLD_OUT
BACK_ORDER
}
inputProductInput{
name:String!
status:ProductStatus!
}
GraphQL
❌ Subgraph B
enumProductStatus{
AVAILABLE
SOLD_OUT
}
inputProductInput{
name:String!
status:ProductStatus!
}
Use instead:
GraphQL
✅ Subgraph A
enumProductStatus{
AVAILABLE
SOLD_OUT
BACK_ORDER
}
inputProductInput{
name:String!
status:ProductStatus!
}
GraphQL
✅ Subgraph B
enumProductStatus{
AVAILABLE
SOLD_OUT
BACK_ORDER
}
inputProductInput{
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
❌ Subgraph A
enumOrderStatus{
CREATED
PROCESSING
COMPLETED
}
typeOrder{
name:String!
status:OrderStatus!
}
GraphQL
❌ Subgraph B
enumOrderStatus{
CREATED
COMPLETED
}
typeOrder{
name:String!
status:OrderStatus!
}
Use instead:
GraphQL
✅ Subgraph A
enumOrderStatus{
CREATED
PROCESSING
COMPLETED
}
typeOrder{
name:String!
status:OrderStatus!
}
GraphQL
✅ Subgraph B
enumOrderStatus{
CREATED
PROCESSING
COMPLETED
}
typeOrder{
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
❌ Subgraph A
directive@log(
message:String!
)onQUERY
GraphQL
❌ Subgraph B
directive@log(
message:String!
)onFIELD
Use instead:
GraphQL
✅ Subgraph A
directive@log(
message:String!
)onQUERY|FIELD
GraphQL
✅ Subgraph B
directive@log(
message:String!
)onQUERY|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
❌ Subgraph A
directive@modify(
field:String!
)onFIELD
GraphQL
❌ Subgraph B
# 🦗🦗🦗
Use instead:
GraphQL
✅ Subgraph A
directive@modify(
field:String!
)onFIELD
GraphQL
✅ Subgraph B
directive@modify(
field:String!
)onFIELD
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
❌ Subgraph A
directive@validateLength(
max:Int!
)repeatableonFIELD
GraphQL
❌ Subgraph B
directive@validateLength(
max:Int!
)onFIELD
Use instead:
GraphQL
✅ Subgraph A
directive@validateLength(
max:Int!
)repeatableonFIELD
GraphQL
✅ Subgraph B
directive@validateLength(
max:Int!
)repeatableonFIELD
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
❌ Subgraph A
inputProductInput{
name:String
price:Float
}
inputOrderInput{
product:ProductInput
}
GraphQL
❌ Subgraph B
inputProductInput{
name:String
}
inputOrderInput{
product:ProductInput
}
Use instead:
GraphQL
✅ Subgraph A
inputProductInput{
name:String
price:Float
}
inputOrderInput{
product:ProductInput
}
GraphQL
✅ Subgraph B
inputProductInput{
name:String
price:Float
}
inputOrderInput{
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
❌ Subgraph A
interfaceProduct{
id:ID!
name:String
cost:Float
}
typeDigitalProductimplementsProduct{
id:ID!
name:String
cost:Float
size:Int
}
GraphQL
❌ Subgraph B
interfaceProduct{
id:ID!
name:String
# cost is not defined in the interface
}
typePhysicalProductimplementsProduct{
id:ID!
name:String
cost:Float
weight:Float
}
Use instead:
GraphQL
✅ Subgraph A
interfaceProduct{
id:ID!
name:String
cost:Float
}
typeDigitalProductimplementsProduct{
id:ID!
name:String
cost:Float
size:Int
}
GraphQL
✅ Subgraph B
interfaceProduct{
id:ID!
name:String
cost:Float
}
typePhysicalProductimplementsProduct{
id:ID!
name:String
cost:Float
weight:Float
}
INCONSISTENT_NON_REPEATABLE_DIRECTIVE_ARGUMENTS
What it does
Checks if a non-repeatabledirective is applied to a schema element across different subgraphs with differing arguments.
Rationale
Inconsistent directiveargument usage can lead to misunderstandings and potential issues in client applications.
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
❌ Subgraph A
typeProduct{
id:ID!@shareable
name:String@shareable
price:Float
}
GraphQL
❌ Subgraph B
typeProduct{
id:ID!@shareable
name:String@shareable
}
Use instead:
GraphQL
✅ Subgraph A
typeProduct@shareable{
id:ID!
name:String
price:Float
}
GraphQL
✅ Subgraph B
typeProduct@shareable{
id:ID!
name:String
price:Float
}
INCONSISTENT_RUNTIME_TYPES_FOR_SHAREABLE_RETURN
What it does
Checks that a @shareablefield returns consistent sets of runtime types in all subgraphs in which it's defined.
Rationale
Each subgraph's resolver for a @shareablefield 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
❌ Subgraph A
typeProduct{
id:ID!
name:String
details:Details@shareable
}
typeDetails{
size:String
}
GraphQL
❌ Subgraph B
typeProduct{
id:ID!
name:String
details:Details@shareable
}
typeDetails{
weight:Float
}
Use instead:
GraphQL
✅ Subgraph A
typeProduct{
id:ID!
name:String
details:Details@shareable
}
typeDetails{
size:String
}
GraphQL
✅ Subgraph B
typeProduct{
id:ID!
name:String
details:Details@shareable
}
typeDetails{
size:String
}
INCONSISTENT_TYPE_SYSTEM_DIRECTIVE_LOCATIONS
What it does
Checks that a type systemdirective 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
❌ Subgraph A
directive@customDirective(
message:String!
)onOBJECT|FIELD_DEFINITION
GraphQL
❌ Subgraph B
directive@customDirective(
message:String!
)onFIELD_DEFINITION
Use instead:
GraphQL
✅ Subgraph A
directive@customDirective(
message:String!
)onOBJECT|FIELD_DEFINITION
GraphQL
✅ Subgraph B
directive@customDirective(
message:String!
)onOBJECT|FIELD_DEFINITION
INCONSISTENT_TYPE_SYSTEM_DIRECTIVE_REPEATABLE
What it does
Checks that a type systemdirective 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
❌ Subgraph A
directive@customDirectiveonOBJECT
GraphQL
❌ Subgraph B
directive@customDirectiverepeatableonOBJECT
Use instead:
GraphQL
✅ Subgraph A
directive@customDirectiverepeatableonOBJECT
GraphQL
✅ Subgraph B
directive@customDirectiverepeatableonOBJECT
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.
Checks for issues when composing custom directives.
MERGED_NON_REPEATABLE_DIRECTIVE_ARGUMENTS
What it does
Checks if a non-repeatabledirective has been applied to the same schema element in different subgraphs with different arguments. Learn more.
Rationale
Arguments should be consistent across a non-repeatabledirective'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 repeatabledirective.
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
❌ Subgraph A
directive@log(message:String!)onQUERY
GraphQL
❌ Subgraph B
directive@log(message:String!)onFIELD
Use instead:
GraphQL
✅ Subgraph A
directive@log(message:String!)onQUERY|FIELD
GraphQL
✅ Subgraph B
directive@log(message:String!)onQUERY|FIELD
FROM_SUBGRAPH_DOES_NOT_EXIST
What it does
Checks that the source subgraph specified by @overridedirective exists.
Rationale
The @overridedirective 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.