Docs
Launch GraphOS Studio

Schema linter rules

Reference


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

Naming rules

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

Fields

FIELD_NAMES_SHOULD_BE_CAMEL_CASE

names should always use camelCase.

schema.graphql
type User {
FirstName: String! # PascalCase
}

schema.graphql
type User {
firstName: String # camelCase
}

RESTY_FIELD_NAMES

A 's name should never start with any of the following verbs:

  • get
  • list
  • post
  • put
  • patch

Most s should not start with any verb, with the exception of Mutation s. For Mutation s, use a verb that more specifically describes the action being performed (such as create, delete, or edit).

schema.graphql
type Query {
getUsers: [User!]!
}

schema.graphql
type Query {
users: [User!]!
}

Types

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

  • Objects
  • Interfaces
  • Inputs
  • Enums
  • Unions

TYPE_NAMES_SHOULD_BE_PASCAL_CASE

All type names should use PascalCase.

schema.graphql
type streamingService { # camelCase
id: ID!
}

schema.graphql
type StreamingService { # PascalCase
id: ID!
}

TYPE_PREFIX

Type names should never use the prefix Type.

schema.graphql
type TypeBook {
title: String!
}

schema.graphql
type Book {
title: String!
}

TYPE_SUFFIX

Type names should never use the suffix Type.

schema.graphql
type BookType {
title: String!
}

schema.graphql
type Book {
title: String!
}

Objects

OBJECT_PREFIX

An 's name should never use the prefix Object.

schema.graphql
type ObjectBook {
title: String!
}

schema.graphql
type Book {
title: String!
}

OBJECT_SUFFIX

An 's name should never use the suffix Object.

schema.graphql
type BookObject {
title: String!
}

schema.graphql
type Book {
title: String!
}

Interfaces

INTERFACE_PREFIX

An interface type's name should never use the prefix Interface.

schema.graphql
interface InterfaceBook {
title: String
author: String
}

schema.graphql
interface Book {
title: String
author: String
}

INTERFACE_SUFFIX

An interface type's name should never use the suffix Interface.

schema.graphql
interface BookInterface {
title: String
author: String
}

schema.graphql
interface Book {
title: String
author: String
}

Inputs & arguments

INPUT_ARGUMENT_NAMES_SHOULD_BE_CAMEL_CASE

A GraphQL 's name should always use camelCase.

schema.graphql
type Mutation {
createBlogPost(BlogPostContent: BlogPostContent!): Post # PascalCase
}

schema.graphql
type Mutation {
createBlogPost(blogPostContent: BlogPostContent!): Post # camelCase
}

INPUT_TYPE_SUFFIX

An input type's name should always use the suffix Input.

schema.graphql
input BlogPostDetails {
title: String!
content: String!
}

schema.graphql
input BlogPostDetailsInput {
title: String!
content: String!
}

Enums

ENUM_VALUES_SHOULD_BE_SCREAMING_SNAKE_CASE

Enum values should always use SCREAMING_SNAKE_CASE.

schema.graphql
enum Amenity {
public_park # snake_case
}

schema.graphql
enum Amenity {
PUBLIC_PARK # SCREAMING_SNAKE_CASE 😱
}

ENUM_PREFIX

An enum type's name should never use the prefix Enum.

schema.graphql
enum EnumResidence {
HOUSE
APARTMENT
CONDO
}

schema.graphql
enum Residence {
HOUSE
APARTMENT
CONDO
}

ENUM_SUFFIX

An enum type's name should never use the suffix Enum.

schema.graphql
enum ResidenceEnum {
HOUSE
APARTMENT
CONDO
}

schema.graphql
enum Residence {
HOUSE
APARTMENT
CONDO
}

ENUM_USED_AS_INPUT_WITHOUT_SUFFIX

If an enum type is used as an input , its name should use the suffix Input.

schema.graphql
enum Role {
EDITOR
VIEWER
}
type Query {
users(role: Role): [User!]!
}

schema.graphql
enum RoleInput {
EDITOR
VIEWER
}
type Query {
users(role: RoleInput): [User!]!
}

ENUM_USED_AS_OUTPUT_DESPITE_SUFFIX

If an enum is used as the return type of a non-input , its name should not use the suffix Input.

schema.graphql
enum RoleInput {
EDITOR
VIEWER
}
type Query {
userRole(userId: ID!): RoleInput
}

schema.graphql
enum Role {
EDITOR
VIEWER
}
type Query {
userRole(userId: ID!): Role
}

Directives

DIRECTIVE_NAMES_SHOULD_BE_CAMEL_CASE

names should always use camelCase.

schema.graphql
directive @SpecialField on FIELD_DEFINITION # PascalCase

schema.graphql
directive @specialField on FIELD_DEFINITION # camelCase

Composition rules
Since 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.

These rules flag potential improvements to a composed of subgraph s.

Inconsistent elements

INCONSISTENT_ARGUMENT_PRESENCE

Indicates that an optional (of a or definition) isn't present in all s and therefore won't be part of the .

Subgraph A
type Product {
id: ID!
name: String
price(currency: Currency): Float
}
Subgraph B
type Product {
id: ID!
name: String
price(currency: Currency, taxIncluded: Boolean): Float
}

Subgraph A
type Product {
id: ID!
name: String
price(currency: Currency, taxIncluded: Boolean): Float
}
Subgraph B
type Product {
id: ID!
name: String
price(currency: Currency, taxIncluded: Boolean): Float
}

INCONSISTENT_BUT_COMPATIBLE_ARGUMENT_TYPE

Indicates that an type (of a , input field, or definition) doesn't have the exact same type in all s, but that the types are compatible.

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.

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

Subgraph A
type Product {
id: ID!
name: String
price(currency: Currency!): Float
}
enum Currency {
USD
EUR
GBP
JPY
AUD
CAD
}
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

Indicates that a doesn't have the exact same types in all s, but that the types are compatible.

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.

Subgraph A
type Product {
id: ID!
name: String
price: Money
}
type Money {
amount: Float!
currency: Currency!
}
enum Currency {
USD
EUR
GBP
JPY
AUD
CAD
}
Subgraph B
type Product {
id: ID!
name: String
price: Money!
}
type Money {
amount: Float!
currency: Currency!
}
enum Currency {
USD
EUR
GBP
JPY
AUD
CAD
}

Subgraph A
type Product {
id: ID!
name: String
price: Money!
}
type Money {
amount: Float!
currency: Currency!
}
enum Currency {
USD
EUR
GBP
JPY
AUD
CAD
}
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

Indicates that an definition (of a , input field, or definition) has a default value in only some of the s that define the argument.

Subgraph A
type Product {
id: ID!
name: String
weight(kg: Float = 1.0): Float
}
Subgraph B
type Product {
id: ID!
name: String
weight(kg: Float): Float
}

Subgraph A
type Product {
id: ID!
name: String
weight(kg: Float = 1.0): Float
}
Subgraph B
type Product {
id: ID!
name: String
weight(kg: Float = 1.0): Float
}

INCONSISTENT_DESCRIPTION

Indicates that an element has a description in more than one , and the descriptions aren't equal.

Subgraph A
"""
A type representing a product.
"""
type Product {
id: ID!
name: String
}
Subgraph B
"""
An object representing a product.
"""
type Product {
id: ID!
name: String
}

Subgraph A
"""
A type representing a product.
"""
type Product {
id: ID!
name: String
}
Subgraph B
"""
A type representing a product.
"""
type Product {
id: ID!
name: String
}

INCONSISTENT_ENTITY

Indicates that an object is declared as an entity (has a @key) in only some of the s in which the object is defined.

Subgraph A
type Product @key(fields: "id") {
id: ID!
name: String
}
Subgraph B
type Product {
id: ID!
stock: Int
}

Subgraph A
type Product @key(fields: "id") {
id: ID!
name: String
}
Subgraph B
type Product @key(fields: "id") {
id: ID!
stock: Int
}

INCONSISTENT_ENUM_VALUE_FOR_INPUT_ENUM

Indicates that a value of an enum type definition, that is only used as an input type, hasn't been merged into the because it's defined in only some of the s that declare the enum.

Subgraph A
enum ProductStatus {
AVAILABLE
SOLD_OUT
BACK_ORDER
}
input ProductInput {
name: String!
status: ProductStatus!
}
Subgraph B
enum ProductStatus {
AVAILABLE
SOLD_OUT
}
input ProductInput {
name: String!
status: ProductStatus!
}

Subgraph A
enum ProductStatus {
AVAILABLE
SOLD_OUT
BACK_ORDER
}
input ProductInput {
name: String!
status: ProductStatus!
}
Subgraph B
enum ProductStatus {
AVAILABLE
SOLD_OUT
BACK_ORDER
}
input ProductInput {
name: String!
status: ProductStatus!
}

INCONSISTENT_ENUM_VALUE_FOR_OUTPUT_ENUM

Indicates that a value of an enum type definition, that is only used as an output type or is unused, has been merged in the but is defined in only some of the s that declare the enum.

Subgraph A
enum OrderStatus {
CREATED
PROCESSING
COMPLETED
}
type Order {
name: String!
status: OrderStatus!
}
Subgraph B
enum OrderStatus {
CREATED
COMPLETED
}
type Order {
name: String!
status: OrderStatus!
}

Subgraph A
enum OrderStatus {
CREATED
PROCESSING
COMPLETED
}
type Order {
name: String!
status: OrderStatus!
}
Subgraph B
enum OrderStatus {
CREATED
PROCESSING
COMPLETED
}
type Order {
name: String!
status: OrderStatus!
}

INCONSISTENT_EXECUTABLE_DIRECTIVE_LOCATIONS

Indicates that an executable definition is declared with inconsistent locations across s. (The then uses the intersection of all locations in the supergraph.)

Subgraph A
directive @log(
message: String!
) on QUERY | FIELD
Subgraph B
directive @log(
message: String!
) on FIELD

Subgraph A
directive @log(
message: String!
) on QUERY | FIELD
Subgraph B
directive @log(
message: String!
) on QUERY | FIELD

INCONSISTENT_EXECUTABLE_DIRECTIVE_PRESENCE

Indicates that an executable definition is declared in only some of the s.

Subgraph A
directive @modify(
field: String!
) on FIELD
Subgraph B
# 🦗🦗🦗

Subgraph A
directive @modify(
field: String!
) on FIELD
Subgraph B
directive @modify(
field: String!
) on FIELD

INCONSISTENT_EXECUTABLE_DIRECTIVE_REPEATABLE

Indicates that an executable definition is marked repeatable in only some of the s and won't be repeatable in the .

Subgraph A
directive @validateLength(
max: Int!
) repeatable on FIELD
Subgraph B
directive @validateLength(
max: Int!
) on FIELD

Subgraph A
directive @validateLength(
max: Int!
) repeatable on FIELD
Subgraph B
directive @validateLength(
max: Int!
) repeatable on FIELD

INCONSISTENT_INPUT_OBJECT_FIELD

Indicates that a of an input definition is only defined in some of the s that declare the input object.

Subgraph A
input ProductInput {
name: String
price: Float
}
input OrderInput {
product: ProductInput
}
Subgraph B
input ProductInput {
name: String
}
input OrderInput {
product: ProductInput
}

Subgraph A
input ProductInput {
name: String
price: Float
}
input OrderInput {
product: ProductInput
}
Subgraph B
input ProductInput {
name: String
price: Float
}
input OrderInput {
product: ProductInput
}

INCONSISTENT_INTERFACE_VALUE_TYPE_FIELD

Indicates that a of an interface "value type" (has no @key in any ) isn't defined in all the subgraphs that declare the type.

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

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

A non-repeatable is applied to a element in different s but with s that are different.

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

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

INCONSISTENT_OBJECT_VALUE_TYPE_FIELD

Indicates that a of an object "value type" (has no @key in any ) isn't defined in all the subgraphs that declare the type.

Subgraph A
type Product {
id: ID!
name: String
price: Float
}
Subgraph B
type Product {
id: ID!
name: String
}

Subgraph A
type Product {
id: ID!
name: String
price: Float
}
Subgraph B
type Product {
id: ID!
name: String
price: Float
}

INCONSISTENT_RUNTIME_TYPES_FOR_SHAREABLE_RETURN

Indicates that a @shareable returns different sets of runtime types in the different s in which it's defined.

Subgraph A
type Product {
id: ID!
name: String
details: Details @shareable
}
type Details {
size: String
}
Subgraph B
type Product {
id: ID!
name: String
details: Details @shareable
}
type Details {
weight: Float
}

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

INCONSISTENT_TYPE_SYSTEM_DIRECTIVE_LOCATIONS

Indicates that an executable definition is declared with inconsistent locations across s. (The then uses the intersection of all locations in the supergraph.)

Subgraph A
directive @customDirective(
message: String!
) on OBJECT | FIELD_DEFINITION
Subgraph B
directive @customDirective(
message: String!
) on FIELD_DEFINITION

Subgraph A
directive @customDirective(
message: String!
) on OBJECT | FIELD_DEFINITION
Subgraph B
directive @customDirective(
message: String!
) on OBJECT | FIELD_DEFINITION

INCONSISTENT_TYPE_SYSTEM_DIRECTIVE_REPEATABLE

Indicates that a type system definition is marked repeatable in only some of the s that declare the directive and will be repeatable in the .

Subgraph A
directive @customDirective on OBJECT
Subgraph B
directive @customDirective repeatable on OBJECT

Subgraph A
directive @customDirective repeatable on OBJECT
Subgraph B
directive @customDirective repeatable on OBJECT

INCONSISTENT_UNION_MEMBER

Indicates that a member of a union type definition is only defined in some of the s that declare the union.

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

Subgraph A
type Product {
id: ID!
name: String
}
type Service {
id: ID!
description: String
}
union SearchResult = Product | Service
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

Indicates a with the @override no longer exists in a source , so the directive can be safely removed.

Subgraph A
type Product @key(fields: "id") {
id: ID!
inStock: Boolean! @override(from: "Subgraph B")
}
Subgraph B
type Product @key(fields: "id") {
id: ID!
name: String!
}

Subgraph A
type Product @key(fields: "id") {
id: ID!
inStock: Boolean!
}
Subgraph B
type Product @key(fields: "id") {
id: ID!
name: String!
}

OVERRIDDEN_FIELD_CAN_BE_REMOVED

Indicates that a has been overridden by another . You should consider removing the overriden field to avoid confusion.

Subgraph A
type Product @key(fields: "id") {
id: ID!
name: String!
inStock: Boolean! @override(from: "Subgraph B")
}
Subgraph B
type Product @key(fields: "id") {
id: ID!
inStock: Boolean!
}

Subgraph A
type Product @key(fields: "id") {
id: ID!
name: String!
}
Subgraph B
type Product @key(fields: "id") {
id: ID!
inStock: Boolean!
}

UNUSED_ENUM_TYPE

Indicates that an enum type is defined in some s but is unused (no / references it). All values from subgraphs defining that enum will be included in the .

Subgraph A
enum ProductStatus {
AVAILABLE
SOLD_OUT
}
type Product {
id: ID!
name: String
}
Subgraph B
type Order {
id: ID!
product: Product
status: String
}

Subgraph A
type Product {
id: ID!
name: String
status: ProductStatus
}
Subgraph B
type Order {
id: ID!
product: Product
status: ProductStatus
}

Directives

DIRECTIVE_COMPOSITION

Indicates that an issue was detected when composing custom s.

MERGED_NON_REPEATABLE_DIRECTIVE_ARGUMENTS

Indicates a non-repeatable has been applied to a element in different s with different s and the arguments values were merged using the directive configured strategies.

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

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

NO_EXECUTABLE_DIRECTIVE_INTERSECTION

Indicates that an executable definition is declared with no shared locations across s.

Subgraph A
directive @log(
message: String!
) on QUERY
Subgraph B
directive @log(
message: String!
) on FIELD

Subgraph A
directive @log(
message: String!
) on QUERY | FIELD
Subgraph B
directive @log(
message: String!
) on QUERY | FIELD

FROM_SUBGRAPH_DOES_NOT_EXIST

Indicates the source specified by @override doesn't exist.

Subgraph A
type Product @key(fields: "id") {
id: ID!
inStock: Boolean! @override(from: "Subgraph B")
}
Subgraph B
# Subgraph B doesn't exist

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

Other rules

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

Schema

These rules apply to the entire .

DOES_NOT_PARSE

The linter raises this violation if it attempts to read a malformed GraphQL schema. Check your schema for any syntax errors.


ALL_ELEMENTS_REQUIRE_DESCRIPTION

Each element in the must include a description.

schema.graphql
type User {
username: String!
}

schema.graphql
"Represents a user"
type User {
"The user's username"
username: String!
}

DEFINED_TYPES_ARE_USED

Every type defined in a should be used at least once.

Unused types commonly appear after you refactor other parts of your . You should remove unused types to prevent confusion.

schema.graphql
type SomeUnusedType { # Also fails the TYPE_SUFFIX rule!
name: String!
}
type AnActuallyUsedType {
name: String!
}
type Query {
hello: String!
title: AnActuallyUsedType
}

schema.graphql
type Book {
title: String!
}
type Query {
books: [Book!]!
}

QUERY_DOCUMENT_DECLARATION

A GraphQL should never include definitions of GraphQL operations (such as queries and s).

schema.graphql
type Query {
users: [User!]!
}
query GetUsers {
# Don't define operations in a schema document
users {
id
}
}

Directives

CONTACT_DIRECTIVE_MISSING

A should always provide owner contact details via the @contact . Learn more.

schema.graphql
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

The @deprecated should always include a reason .

schema.graphql
type Product {
title: String @deprecated
name: String!
}

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

TAG_DIRECTIVE_USES_UNKNOWN_NAME

The @tag should always use an approved value for its name . You specify approved values in GraphOS Studio.

Previous
Overview
Next
Publishing schemas
Edit on GitHubEditForumsDiscord