Schema Linter Rules

Reference for GraphOS linting rules with examples


This reference lists the rules that you can enforce with GraphOS schema linting, along with the code that GraphOS returns for each rule violation.

Naming rules

These rules enforce naming conventions. Rules are categorized by the part(s) of your schema that they correspond to.

Fields

FIELD_NAMES_SHOULD_BE_CAMEL_CASE

What it does

Checks that all field names use camelCase.

Rationale

camelCase is a convention for field names in GraphQL.

Examples

The following example violates the rule:
GraphQL
❌ schema.graphql
type User {
  FirstName: String! # PascalCase
}

Use instead:
GraphQL
✅ schema.graphql
type User {
  firstName: String # camelCase
}
RESTY_FIELD_NAMES

What it does

Checks that a field's name doesn't start with any of the following verbs: get, list, post, put, patch.

Rationale

Fields should not start with a verb, except for mutations. For mutation fields, it's best to use a verb that describes the specific action being performed, for example, create, delete, or edit.

Examples

The following example violates the rule:
GraphQL
❌ schema.graphql
type Query {
  getUsers: [User!]!
}

Use instead:
GraphQL
✅ schema.graphql
type Query {
  users: [User!]!
}

Types

These rules apply to all types that appear in a GraphQL schema, including:

  • Objects

  • Interfaces

  • Inputs

  • Enums

  • Unions

TYPE_NAMES_SHOULD_BE_PASCAL_CASE

What it does

Checks that all type names use PascalCase.

Rationale

PascalCase is a convention for type names in GraphQL.

Examples

The following example violates the rule:
GraphQL
❌ schema.graphql
type streamingService { # camelCase
  id: ID!
}

Use instead:
GraphQL
✅ schema.graphql
type StreamingService { # PascalCase
  id: ID!
}
TYPE_PREFIX

What it does

Checks that type names don't start with the prefix Type.

Rationale

Avoid using the redundant Type prefix to shorten your schema definitions and improve readability.

Examples

The following example violates the rule:
GraphQL
❌ schema.graphql
type TypeBook {
  title: String!
}

Use instead:
GraphQL
✅ schema.graphql
type Book {
  title: String!
}
TYPE_SUFFIX

What it does

Checks that type names don't use the suffix Type.

Rationale

Avoid using the redundant Type suffix to shorten your schema definitions and improve readability.

Examples

The following example violates the rule:
GraphQL
❌ schema.graphql
type BookType {
  title: String!
}

Use instead:
GraphQL
✅ schema.graphql
type Book {
  title: String!
}

Objects

OBJECT_PREFIX

What it does

Checks that object type names don't start with the prefix Object.

Rationale

Avoid using the redundant Object prefix to shorten your schema definitions and improve readability.

Examples

The following example violates the rule:
GraphQL
❌ schema.graphql
type ObjectBook {
  title: String!
}

Use instead:
GraphQL
✅ schema.graphql
type Book {
  title: String!
}
OBJECT_SUFFIX

What it does

Checks that object type names don't use the suffix Object.

Rationale

Avoid using the redundant Object suffix to shorten your schema definitions and improve readability.

Examples

The following example violates the rule:
GraphQL
❌ schema.graphql
type BookObject {
  title: String!
}

Use instead:
GraphQL
✅ schema.graphql
type Book {
  title: String!
}

Interfaces

INTERFACE_PREFIX

What it does

Checks that interface type names don't start with the prefix Interface.

Rationale

Avoid using the redundant Interface prefix to shorten your schema definitions and improve readability.

Examples

The following example violates the rule:
GraphQL
❌ schema.graphql
interface InterfaceBook {
  title: String
  author: String
}

Use instead:
GraphQL
✅ schema.graphql
interface Book {
  title: String
  author: String
}
INTERFACE_SUFFIX

What it does

Checks that interface type names don't use the suffix Interface.

Rationale

Avoid using the redundant Interface suffix to shorten your schema definitions and improve readability.

Examples

The following example violates the rule:
GraphQL
❌ schema.graphql
interface BookInterface {
  title: String
  author: String
}

Use instead:
GraphQL
✅ schema.graphql
interface Book {
  title: String
  author: String
}

Inputs and arguments

INPUT_ARGUMENT_NAMES_SHOULD_BE_CAMEL_CASE

What it does

Checks that argument names use camelCase.

Rationale

camelCase is a convention for argument names in GraphQL.

Examples

The following example violates the rule:
GraphQL
❌ schema.graphql
type Mutation {
  createBlogPost(BlogPostContent: BlogPostContent!): Post # PascalCase
}

Use instead:
GraphQL
✅ schema.graphql
type Mutation {
  createBlogPost(blogPostContent: BlogPostContent!): Post # camelCase
}
INPUT_TYPE_SUFFIX

What it does

Checks that input type names use the suffix Input.

Rationale

Ending input type names with Input distinguishes input types from "output" types like objects.

Examples

The following example violates the rule:
GraphQL
❌ schema.graphql
input BlogPostDetails {
  title: String!
  content: String!
}

Use instead:
GraphQL
✅ schema.graphql
input BlogPostDetailsInput {
  title: String!
  content: String!
}

Enums

ENUM_VALUES_SHOULD_BE_SCREAMING_SNAKE_CASE

What it does

Checks that enum values use SCREAMING_SNAKE_CASE.

Rationale

SCREAMING_SNAKE_CASE is a convention for enum values in GraphQL.

Examples

The following example violates the rule:
GraphQL
❌ schema.graphql
enum Amenity {
  public_park # snake_case
}

Use instead:
GraphQL
✅ schema.graphql
enum Amenity {
  PUBLIC_PARK # SCREAMING_SNAKE_CASE 😱
}
ENUM_PREFIX

What it does

Checks that enum type names don't start with the prefix Enum.

Rationale

Avoid using the redundant Enum prefix to shorten your schema definitions and improve readability.

Examples

The following example violates the rule:
GraphQL
❌ schema.graphql
enum EnumResidence {
  HOUSE
  APARTMENT
  CONDO
}

Use instead:
GraphQL
✅ schema.graphql
enum Residence {
  HOUSE
  APARTMENT
  CONDO
}
ENUM_SUFFIX

What it does

Checks that enum type names don't use the suffix Enum.

Rationale

Avoid using the redundant Enum suffix to shorten your schema definitions and improve readability.

Examples

The following example violates the rule:
GraphQL
❌ schema.graphql
enum ResidenceEnum {
  HOUSE
  APARTMENT
  CONDO
}

Use instead:
GraphQL
✅ schema.graphql
enum Residence {
  HOUSE
  APARTMENT
  CONDO
}
ENUM_USED_AS_INPUT_WITHOUT_SUFFIX

What it does

If an enum type is used as an input argument, checks that its name uses the suffix Input.

Rationale

Ending input arguments with Input distinguishes inputs from "output" types like objects.

Examples

The following example violates the rule:
GraphQL
❌ schema.graphql
enum Role {
  EDITOR
  VIEWER
}

type Query {
  users(role: Role): [User!]!
}

Use instead:
GraphQL
✅ schema.graphql
enum RoleInput {
  EDITOR
  VIEWER
}

type Query {
  users(role: RoleInput): [User!]!
}
ENUM_USED_AS_OUTPUT_DESPITE_SUFFIX

What it does

If an enum is used as the return type of a non-input field, checks that its name doesn't use the suffix Input.

Rationale

Including the suffix Input on an output type is misleading.

Examples

The following example violates the rule:
GraphQL
❌ schema.graphql
enum RoleInput {
  EDITOR
  VIEWER
}

type Query {
  userRole(userId: ID!): RoleInput
}

Use instead:
GraphQL
✅ schema.graphql
enum Role {
  EDITOR
  VIEWER
}

type Query {
  userRole(userId: ID!): Role
}

Directives

DIRECTIVE_NAMES_SHOULD_BE_CAMEL_CASE

What it does

Checks that directive names use camelCase.

Rationale

camelCase is a convention for directive names in GraphQL.

Examples

The following example violates the rule:
GraphQL
❌ schema.graphql
directive @SpecialField on FIELD_DEFINITION # PascalCase

Use instead:
GraphQL
✅ schema.graphql
directive @specialField on FIELD_DEFINITION # camelCase

Composition rulesSince 2.4

 note
Composition rules are only available for graphs on federation version 2.4 or later. You can update a graph's version from its Settings page in GraphOS Studio.

Composition rules flag potential improvements to subgraph schemas used to compose a supergraph schema.

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 A
type Product {
  id: ID!
  name: String
  price: Float
}
GraphQL
Subgraph B
type Product {
  id: ID!
  name: String
  price: String
}

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 A
type Product {
  id: ID!
  name: String
  price: Float
}
GraphQL
Subgraph B
type Product {
  id: ID!
  name: String
  price: [Float]!
}
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
❌ Subgraph A
type Product {
  id: ID!
  name: String
  price(currency: Currency): Float
}
GraphQL
❌ Subgraph B
type Product {
  id: ID!
  name: String
  price(currency: Currency, taxIncluded: Boolean): Float
}

Use instead:
GraphQL
✅ Subgraph A
type Product {
  id: ID!
  name: String
  price(currency: Currency, taxIncluded: Boolean): Float
}
GraphQL
✅ Subgraph B
type Product {
  id: ID!
  name: String
  price(currency: Currency, taxIncluded: Boolean): Float
}
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
❌ Subgraph A
type Product {
  id: ID!
  name: String
  price(currency: Currency!): Float
}

enum Currency {
  USD
  EUR
  GBP
  JPY
  AUD
  CAD
}
GraphQL
❌ Subgraph B
type Product {
  id: ID!
  name: String
  price(currency: Currency): Float
}

enum Currency {
  USD
  EUR
  GBP
  JPY
  AUD
  CAD
}

Use instead:
GraphQL
✅ Subgraph A
type Product {
  id: ID!
  name: String
  price(currency: Currency!): Float
}

enum Currency {
  USD
  EUR
  GBP
  JPY
  AUD
  CAD
}
GraphQL
✅ Subgraph B
type Product {
  id: ID!
  name: String
  price(currency: Currency!): Float
}

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
❌ Subgraph A
type Product {
  id: ID!
  name: String
  price: Money
}

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

enum Currency {
  USD
  EUR
  GBP
  JPY
  AUD
  CAD
}
GraphQL
❌ Subgraph B
type Product {
  id: ID!
  name: String
  price: Money!
}

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

enum Currency {
  USD
  EUR
  GBP
  JPY
  AUD
  CAD
}

Use instead:
GraphQL
✅ Subgraph A
type Product {
  id: ID!
  name: String
  price: Money!
}

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

enum Currency {
  USD
  EUR
  GBP
  JPY
  AUD
  CAD
}
GraphQL
✅ Subgraph B
type Product {
  id: ID!
  name: String
  price: Money!
}

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
❌ Subgraph A
type Product {
  id: ID!
  name: String
  weight(kg: Float = 1.0): Float
}
GraphQL
❌ Subgraph B
type Product {
  id: ID!
  name: String
  weight(kg: Float): Float
}

Use instead:
GraphQL
✅ Subgraph A
type Product {
  id: ID!
  name: String
  weight(kg: Float = 1.0): Float
}
GraphQL
✅ Subgraph B
type Product {
  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.
"""
type Product {
  id: ID!
  name: String
}
GraphQL
❌ Subgraph B
"""
An object representing a product.
"""
type Product {
  id: ID!
  name: String
}

Use instead:
GraphQL
✅ Subgraph A
"""
A type representing a product.
"""
type Product {
  id: ID!
  name: String
}
GraphQL
✅ Subgraph B
"""
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
❌ Subgraph A
type Product
  @key(fields: "id") {
  id: ID!
  name: String
}
GraphQL
❌ Subgraph B
type Product {
  id: ID!
  stock: Int
}

Use instead:
GraphQL
✅ Subgraph A
type Product
  @key(fields: "id") {
  id: ID!
  name: String
}
GraphQL
✅ Subgraph B
type Product
  @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
enum ProductStatus {
  AVAILABLE
  SOLD_OUT
  BACK_ORDER
}

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

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

Use instead:
GraphQL
✅ Subgraph A
enum ProductStatus {
  AVAILABLE
  SOLD_OUT
  BACK_ORDER
}

input ProductInput {
  name: String!
  status: ProductStatus!
}
GraphQL
✅ Subgraph B
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
❌ Subgraph A
enum OrderStatus {
  CREATED
  PROCESSING
  COMPLETED
}

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

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

Use instead:
GraphQL
✅ Subgraph A
enum OrderStatus {
  CREATED
  PROCESSING
  COMPLETED
}

type Order {
  name: String!
  status: OrderStatus!
}
GraphQL
✅ Subgraph B
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
❌ Subgraph A
directive @log(message: String!) on QUERY
GraphQL
❌ Subgraph B
directive @log(message: String!) on FIELD

Use instead:
GraphQL
✅ Subgraph A
directive @log(message: String!) on QUERY | FIELD
GraphQL
✅ Subgraph B
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
❌ Subgraph A
directive @modify(field: String!) on FIELD
GraphQL
❌ Subgraph B
# 🦗🦗🦗

Use instead:
GraphQL
✅ Subgraph A
directive @modify(field: String!) on FIELD
GraphQL
✅ Subgraph B
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
❌ Subgraph A
directive @validateLength(max: Int!) repeatable on FIELD
GraphQL
❌ Subgraph B
directive @validateLength(max: Int!) on FIELD

Use instead:
GraphQL
✅ Subgraph A
directive @validateLength(max: Int!) repeatable on FIELD
GraphQL
✅ Subgraph B
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
❌ Subgraph A
input ProductInput {
  name: String
  price: Float
}

input OrderInput {
  product: ProductInput
}
GraphQL
❌ Subgraph B
input ProductInput {
  name: String
}

input OrderInput {
  product: ProductInput
}

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

input OrderInput {
  product: ProductInput
}
GraphQL
✅ Subgraph B
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
❌ Subgraph A
interface Product {
  id: ID!
  name: String
  cost: Float
}

type DigitalProduct implements Product {
  id: ID!
  name: String
  cost: Float
  size: Int
}
GraphQL
❌ Subgraph B
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
✅ Subgraph A
interface Product {
  id: ID!
  name: String
  cost: Float
}

type DigitalProduct implements Product {
  id: ID!
  name: String
  cost: Float
  size: Int
}
GraphQL
✅ Subgraph B
interface Product {
  id: ID!
  name: String
  cost: Float
}

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
❌ Subgraph A
type Product {
  id: ID!
  name: String
}

type Query {
  allProducts: [Product] @customDirective(orderBy: "name")
}
GraphQL
❌ Subgraph B
type Product {
  id: ID!
  name: String
}

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

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

type Query {
  allProducts: [Product] @customDirective(orderBy: "name")
}
GraphQL
✅ Subgraph B
type Product {
  id: ID!
  name: String
}

type Query {
  allProducts: [Product] @customDirective(orderBy: "name")
}
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
❌ Subgraph A
type Product {
  id: ID! @shareable
  name: String @shareable
  price: Float
}
GraphQL
❌ Subgraph B
type Product {
  id: ID! @shareable
  name: String @shareable
}

Use instead:
GraphQL
✅ Subgraph A
type Product @shareable {
  id: ID!
  name: String
  price: Float
}
GraphQL
✅ Subgraph B
type Product @shareable {
  id: ID!
  name: String
  price: Float
}
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
❌ Subgraph A
type Product {
  id: ID!
  name: String
  details: Details @shareable
}

type Details {
  size: String
}
GraphQL
❌ Subgraph B
type Product {
  id: ID!
  name: String
  details: Details @shareable
}

type Details {
  weight: Float
}

Use instead:
GraphQL
✅ Subgraph A
type Product {
  id: ID!
  name: String
  details: Details @shareable
}

type Details {
  size: String
}
GraphQL
✅ Subgraph B
type Product {
  id: ID!
  name: String
  details: Details @shareable
}

type Details {
  size: String
}
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
❌ Subgraph A
directive @customDirective(message: String!) on OBJECT | FIELD_DEFINITION
GraphQL
❌ Subgraph B
directive @customDirective(message: String!) on FIELD_DEFINITION

Use instead:
GraphQL
✅ Subgraph A
directive @customDirective(message: String!) on OBJECT | FIELD_DEFINITION
GraphQL
✅ Subgraph B
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
❌ Subgraph A
directive @customDirective on OBJECT
GraphQL
❌ Subgraph B
directive @customDirective repeatable on OBJECT

Use instead:
GraphQL
✅ Subgraph A
directive @customDirective repeatable on OBJECT
GraphQL
✅ Subgraph B
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
❌ Subgraph A
type Product {
  id: ID!
  name: String
}

type Service {
  id: ID!
  description: String
}

union SearchResult = Product | Service
GraphQL
❌ Subgraph B
type Product {
  id: ID!
  name: String
}

union SearchResult = Product

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

type Service {
  id: ID!
  description: String
}

union SearchResult = Product | Service
GraphQL
✅ Subgraph B
type Product {
  id: ID!
  name: String
}

type Service {
  id: ID!
  description: String
}

union SearchResult = Product | Service

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
❌ Subgraph A
type Product @key(fields: "id") {
  id: ID!
  inStock: Boolean! @override(from: "Subgraph B")
}
GraphQL
❌ Subgraph B
type Product @key(fields: "id") {
  id: ID!
  name: String!
}

Use instead:
GraphQL
✅ Subgraph A
type Product @key(fields: "id") {
  id: ID!
  inStock: Boolean!
}
GraphQL
✅ Subgraph B
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
❌ Subgraph A
type Product @key(fields: "id") {
  id: ID!
  inStock: Boolean! @override(from: "Subgraph B")
}
GraphQL
❌ Subgraph B
type Product @key(fields: "id") {
  id: ID!
  name: String!
  inStock: Boolean!
}

Use instead:
GraphQL
✅ Subgraph A
type Product @key(fields: "id") {
  id: ID!
  name: String!
  inStock: Boolean!
}
GraphQL
✅ Subgraph B
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
❌ Subgraph A
type Product @key(fields: "id") {
  id: ID!
  inStock: Boolean! @override(from: "Subgraph B", label: "percent(50)")
}
GraphQL
❌ Subgraph B
type Product @key(fields: "id") {
  id: ID!
  name: String!
  inStock: Boolean!
}

After completing the migration, use instead:
GraphQL
✅ Subgraph A
type Product @key(fields: "id") {
  id: ID!
  name: String!
  inStock: Boolean!
}
GraphQL
✅ Subgraph B
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
❌ Subgraph A
enum ProductStatus {
  AVAILABLE
  SOLD_OUT
}

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

Use instead:
GraphQL
✅ Subgraph A
enum ProductStatus {
  AVAILABLE
  SOLD_OUT
}

type Product {
  id: ID!
  name: String
  status: ProductStatus
}
GraphQL
✅ Subgraph B
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
❌ Subgraph A
type Product {
  id: ID!
  name: String
}

type Query {
  products: [Product] @customDirective(orderBy: ["name"])
}
GraphQL
❌ Subgraph B
type Product {
  id: ID!
  name: String
}

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

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

type Query {
  products: [Product] @customDirective(orderBy: ["name", "price"])
}
GraphQL
✅ Subgraph B
type Product {
  id: ID!
  name: String
}

type Query {
  products: [Product] @customDirective(orderBy: ["name", "price"])
}
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
❌ Subgraph A
directive @log(message: String!) on QUERY
GraphQL
❌ Subgraph B
directive @log(message: String!) on FIELD

Use instead:
GraphQL
✅ Subgraph A
directive @log(message: String!) on QUERY | FIELD
GraphQL
✅ Subgraph B
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
❌ Subgraph A
type Product @key(fields: "id") {
  id: ID!
  inStock: Boolean! @override(from: "Subgraph B")
}
GraphQL
❌ Subgraph B
# Subgraph B doesn't exist

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

Other rules

These rules define conventions for the entire schema and directive usage outside of composition.

Schema

These rules apply to the entire schema.

DOES_NOT_PARSE

What it does

Checks for malformed GraphQL schemas. To resolve, check your schema for syntax errors.

ALL_ELEMENTS_REQUIRE_DESCRIPTION

What it does

Checks that each element in the schema includes a description.

Rationale

Descriptions document your GraphQL schema so consumers of your graph can easily discover fields and learn how to use them. See examples.

Examples

The following example violates the rule:
GraphQL
❌ schema.graphql
type User {
  username: String!
}

Use instead:
GraphQL
✅ schema.graphql
"Represents a user"
type User {
 "A username must be [8-64] characters."
 username: String!
}
DEFINED_TYPES_ARE_UNUSED

What it does

Checks that every type defined in a schema is used at least once.

Rationale

Unused types waste resources and should be immediately removed once they've been refactored out of a schema.

Examples

The following example violates the rule:
GraphQL
❌ schema.graphql
type SomeUnusedType { # Also fails the TYPE_SUFFIX rule!
  name: String!
}

type AnActuallyUsedType {
  name: String!
}

type Query {
  hello: String!
  title: AnActuallyUsedType
}

Use instead:
GraphQL
✅ schema.graphql
type Book {
  title: String!
}

type Query {
  books: [Book!]!
}
QUERY_DOCUMENT_DECLARATION

What it does

Checks that schemas don't define operations, such as queries and mutations.

Rationale

Operations should be defined on the client side, not within schemas. Schemas define types, fields, and their relationships. Operations specify what data to fetch or change. Since what to fetch or change is a client concern, this separation allows for a clean and modular architecture, making it easier to manage and evolve the API over time.

Examples

The following example violates the rule:
GraphQL
❌ schema.graphql
type Query {
  users: [User!]!
}

query GetUsers {
  # Don't define operations in a schema document
  users {
    id
  }
}

Directives

CONTACT_DIRECTIVE_MISSING

What it does

Checks that subgraph schemas always provide owner contact details via the @contact directive.

Rationale

You can use the @contact directive to add your team's contact info to a subgraph schema. This info is displayed in GraphOS Studio, which helps other teams know who to contact for assistance with the subgraph. Learn more.

Examples

This example shows correct @contact directive usage:
GraphQL
✅ schema.graphql
"Annotate a schema with contact information for the subgraph owner"
directive @contact(
  "Contact title of the subgraph owner"
  name: String!
  "URL where the subgraph's owner can be reached"
  url: String
  "Other relevant notes can be included here; supports markdown links"
  description: String
) on SCHEMA

extend schema
  @contact(
    name: "Products Team"
    url: "https://myteam.slack.com/archives/teams-chat-room-url"
    description: "Send urgent issues to [#oncall](https://yourteam.slack.com/archives/oncall)."
  )
DEPRECATED_DIRECTIVE_MISSING_REASON

What it does

Checks that the @deprecated directive always includes a reason argument.

Rationale

The reason argument can help graph consumers understand which field to use instead of the @deprecated one.

Examples

The following example violates the rule:
GraphQL
❌ schema.graphql
type Product {
  title: String @deprecated
  name: String!
}

Use instead:
GraphQL
✅ schema.graphql
type Product {
  title: String @deprecated(reason: "Use Product.name instead")
  name: String!
}
TAG_DIRECTIVE_USES_UNKNOWN_NAME

What it does

Checks that the @tag directive uses an approved value for its name argument. You specify approved values in GraphOS Studio.

Rationale

This directive is used most commonly with GraphOS contracts. Validating @tag names ensures contracts work as intended.

Custom rules

The schema linter is configurable with the predefined rules documented above. Custom rule creation isn't currently supported but is under consideration.

Feedback

Forums