Schema linter rules
Reference
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
Field names should always use camelCase
.
❌
type User {FirstName: String! # PascalCase}
✅
type User {firstName: String # camelCase}
RESTY_FIELD_NAMES
A field's name should never start with any of the following verbs:
get
list
post
put
patch
Most fields should not start with any verb, with the exception of Mutation
fields. For Mutation
fields, use a verb that more specifically describes the action being performed (such as create
, delete
, or edit
).
❌
type Query {getUsers: [User!]!}
✅
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
All type names should use PascalCase
.
❌
type streamingService { # camelCaseid: ID!}
✅
type StreamingService { # PascalCaseid: ID!}
TYPE_PREFIX
Type names should never use the prefix Type
.
❌
type TypeBook {title: String!}
✅
type Book {title: String!}
TYPE_SUFFIX
Type names should never use the suffix Type
.
❌
type BookType {title: String!}
✅
type Book {title: String!}
Objects
OBJECT_PREFIX
An object type's name should never use the prefix Object
.
❌
type ObjectBook {title: String!}
✅
type Book {title: String!}
OBJECT_SUFFIX
An object type's name should never use the suffix Object
.
❌
type BookObject {title: String!}
✅
type Book {title: String!}
Interfaces
INTERFACE_PREFIX
An interface type's name should never use the prefix Interface
.
❌
interface InterfaceBook {title: Stringauthor: String}
✅
interface Book {title: Stringauthor: String}
INTERFACE_SUFFIX
An interface type's name should never use the suffix Interface
.
❌
interface BookInterface {title: Stringauthor: String}
✅
interface Book {title: Stringauthor: String}
Inputs & arguments
INPUT_ARGUMENT_NAMES_SHOULD_BE_CAMEL_CASE
A GraphQL argument's name should always use camelCase
.
❌
type Mutation {createBlogPost(BlogPostContent: BlogPostContent!): Post # PascalCase}
✅
type Mutation {createBlogPost(blogPostContent: BlogPostContent!): Post # camelCase}
INPUT_TYPE_SUFFIX
An input type's name should always use the suffix Input
.
❌
input BlogPostDetails {title: String!content: String!}
✅
input BlogPostDetailsInput {title: String!content: String!}
Enums
ENUM_VALUES_SHOULD_BE_SCREAMING_SNAKE_CASE
Enum values should always use SCREAMING_SNAKE_CASE
.
❌
enum Amenity {public_park # snake_case}
✅
enum Amenity {PUBLIC_PARK # SCREAMING_SNAKE_CASE 😱}
ENUM_PREFIX
An enum type's name should never use the prefix Enum
.
❌
enum EnumResidence {HOUSEAPARTMENTCONDO}
✅
enum Residence {HOUSEAPARTMENTCONDO}
ENUM_SUFFIX
An enum type's name should never use the suffix Enum
.
❌
enum ResidenceEnum {HOUSEAPARTMENTCONDO}
✅
enum Residence {HOUSEAPARTMENTCONDO}
ENUM_USED_AS_INPUT_WITHOUT_SUFFIX
If an enum type is used as an input argument, its name should use the suffix Input
.
❌
enum Role {EDITORVIEWER}type Query {users(role: Role): [User!]!}
✅
enum RoleInput {EDITORVIEWER}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 field, its name should not use the suffix Input
.
❌
enum RoleInput {EDITORVIEWER}type Query {userRole(userId: ID!): RoleInput}
✅
enum Role {EDITORVIEWER}type Query {userRole(userId: ID!): Role}
Directives
DIRECTIVE_NAMES_SHOULD_BE_CAMEL_CASE
Directive names should always use camelCase
.
❌
directive @SpecialField on FIELD_DEFINITION # PascalCase
✅
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.
These rules flag potential improvements to a supergraph schema composed of subgraph schemas.
Inconsistent elements
INCONSISTENT_ARGUMENT_PRESENCE
Indicates that an optional argument (of a field or directive definition) isn't present in all subgraphs and therefore won't be part of the supergraph.
❌
type Product {id: ID!name: Stringprice(currency: Currency): Float}
type Product {id: ID!name: Stringprice(currency: Currency, taxIncluded: Boolean): Float}
✅
type Product {id: ID!name: Stringprice(currency: Currency, taxIncluded: Boolean): Float}
type Product {id: ID!name: Stringprice(currency: Currency, taxIncluded: Boolean): Float}
INCONSISTENT_BUT_COMPATIBLE_ARGUMENT_TYPE
Indicates that an argument type (of a field, input field, or directive definition) doesn't have the exact same type in all subgraphs, 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.
❌
type Product {id: ID!name: Stringprice(currency: Currency!): Float}enum Currency {USDEURGBPJPYAUDCAD}
type Product {id: ID!name: Stringprice(currency: Currency): Float}enum Currency {USDEURGBPJPYAUDCAD}
✅
type Product {id: ID!name: Stringprice(currency: Currency!): Float}enum Currency {USDEURGBPJPYAUDCAD}
type Product {id: ID!name: Stringprice(currency: Currency!): Float}enum Currency {USDEURGBPJPYAUDCAD}
INCONSISTENT_BUT_COMPATIBLE_FIELD_TYPE
Indicates that a field doesn't have the exact same types in all subgraphs, 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.
❌
type Product {id: ID!name: Stringprice: Money}type Money {amount: Float!currency: Currency!}enum Currency {USDEURGBPJPYAUDCAD}
type Product {id: ID!name: Stringprice: Money!}type Money {amount: Float!currency: Currency!}enum Currency {USDEURGBPJPYAUDCAD}
✅
type Product {id: ID!name: Stringprice: Money!}type Money {amount: Float!currency: Currency!}enum Currency {USDEURGBPJPYAUDCAD}
type Product {id: ID!name: Stringprice: Money!}type Money {amount: Float!currency: Currency!}enum Currency {USDEURGBPJPYAUDCAD}
INCONSISTENT_DEFAULT_VALUE_PRESENCE
Indicates that an argument definition (of a field, input field, or directive definition) has a default value in only some of the subgraphs that define the argument.
❌
type Product {id: ID!name: Stringweight(kg: Float = 1.0): Float}
type Product {id: ID!name: Stringweight(kg: Float): Float}
✅
type Product {id: ID!name: Stringweight(kg: Float = 1.0): Float}
type Product {id: ID!name: Stringweight(kg: Float = 1.0): Float}
INCONSISTENT_DESCRIPTION
Indicates that an element has a description in more than one subgraph, and the descriptions aren't equal.
❌
"""A type representing a product."""type Product {id: ID!name: String}
"""An object representing a product."""type Product {id: ID!name: String}
✅
"""A type representing a product."""type Product {id: ID!name: String}
"""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 subgraphs in which the object is defined.
❌
type Product @key(fields: "id") {id: ID!name: String}
type Product {id: ID!stock: Int}
✅
type Product @key(fields: "id") {id: ID!name: String}
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 supergraph because it's defined in only some of the subgraphs that declare the enum.
❌
enum ProductStatus {AVAILABLESOLD_OUTBACK_ORDER}input ProductInput {name: String!status: ProductStatus!}
enum ProductStatus {AVAILABLESOLD_OUT}input ProductInput {name: String!status: ProductStatus!}
✅
enum ProductStatus {AVAILABLESOLD_OUTBACK_ORDER}input ProductInput {name: String!status: ProductStatus!}
enum ProductStatus {AVAILABLESOLD_OUTBACK_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 supergraph but is defined in only some of the subgraphs that declare the enum.
❌
enum OrderStatus {CREATEDPROCESSINGCOMPLETED}type Order {name: String!status: OrderStatus!}
enum OrderStatus {CREATEDCOMPLETED}type Order {name: String!status: OrderStatus!}
✅
enum OrderStatus {CREATEDPROCESSINGCOMPLETED}type Order {name: String!status: OrderStatus!}
enum OrderStatus {CREATEDPROCESSINGCOMPLETED}type Order {name: String!status: OrderStatus!}
INCONSISTENT_EXECUTABLE_DIRECTIVE_LOCATIONS
Indicates that an executable directive definition is declared with inconsistent locations across subgraphs. (The supergraph schema then uses the intersection of all locations in the supergraph.)
❌
directive @log(message: String!) on QUERY | FIELD
directive @log(message: String!) on FIELD
✅
directive @log(message: String!) on QUERY | FIELD
directive @log(message: String!) on QUERY | FIELD
INCONSISTENT_EXECUTABLE_DIRECTIVE_PRESENCE
Indicates that an executable directive definition is declared in only some of the subgraphs.
❌
directive @modify(field: String!) on FIELD
# 🦗🦗🦗
✅
directive @modify(field: String!) on FIELD
directive @modify(field: String!) on FIELD
INCONSISTENT_EXECUTABLE_DIRECTIVE_REPEATABLE
Indicates that an executable directive definition is marked repeatable in only some of the subgraphs and won't be repeatable in the supergraph.
❌
directive @validateLength(max: Int!) repeatable on FIELD
directive @validateLength(max: Int!) on FIELD
✅
directive @validateLength(max: Int!) repeatable on FIELD
directive @validateLength(max: Int!) repeatable on FIELD
INCONSISTENT_INPUT_OBJECT_FIELD
Indicates that a field of an input object type definition is only defined in some of the subgraphs that declare the input object.
❌
input ProductInput {name: Stringprice: Float}input OrderInput {product: ProductInput}
input ProductInput {name: String}input OrderInput {product: ProductInput}
✅
input ProductInput {name: Stringprice: Float}input OrderInput {product: ProductInput}
input ProductInput {name: Stringprice: Float}input OrderInput {product: ProductInput}
INCONSISTENT_INTERFACE_VALUE_TYPE_FIELD
Indicates that a field of an interface "value type" (has no @key
in any subgraph) isn't defined in all the subgraphs that declare the type.
❌
interface Product {id: ID!name: Stringcost: Float}type DigitalProduct implements Product {id: ID!name: Stringcost: Floatsize: Int}
interface Product {id: ID!name: String# cost is not defined in the interface}type PhysicalProduct implements Product {id: ID!name: Stringcost: Floatweight: Float}
✅
interface Product {id: ID!name: Stringcost: Float}type DigitalProduct implements Product {id: ID!name: Stringcost: Floatsize: Int}
interface Product {id: ID!name: Stringcost: Float}type PhysicalProduct implements Product {id: ID!name: Stringcost: Floatweight: Float}
INCONSISTENT_NON_REPEATABLE_DIRECTIVE_ARGUMENTS
A non-repeatable directive is applied to a schema element in different subgraphs but with arguments that are different.
❌
type Product {id: ID!name: String}type Query {allProducts: [Product] @customDirective(orderBy: "name")}
type Product {id: ID!name: String}type Query {allProducts: [Product] @customDirective(orderBy: "price")}
✅
type Product {id: ID!name: String}type Query {allProducts: [Product] @customDirective(orderBy: "name")}
type Product {id: ID!name: String}type Query {allProducts: [Product] @customDirective(orderBy: "name")}
INCONSISTENT_OBJECT_VALUE_TYPE_FIELD
Indicates that a field of an object "value type" (has no @key
in any subgraph) isn't defined in all the subgraphs that declare the type.
❌
type Product {id: ID!name: Stringprice: Float}
type Product {id: ID!name: String}
✅
type Product {id: ID!name: Stringprice: Float}
type Product {id: ID!name: Stringprice: Float}
INCONSISTENT_RUNTIME_TYPES_FOR_SHAREABLE_RETURN
Indicates that a @shareable field returns different sets of runtime types in the different subgraphs in which it's defined.
❌
type Product {id: ID!name: Stringdetails: Details @shareable}type Details {size: String}
type Product {id: ID!name: Stringdetails: Details @shareable}type Details {weight: Float}
✅
type Product {id: ID!name: Stringdetails: Details @shareable}type Details {size: String}
type Product {id: ID!name: Stringdetails: Details @shareable}type Details {size: String}
INCONSISTENT_TYPE_SYSTEM_DIRECTIVE_LOCATIONS
Indicates that an executable directive definition is declared with inconsistent locations across subgraphs. (The supergraph schema then uses the intersection of all locations in the supergraph.)
❌
directive @customDirective(message: String!) on OBJECT | FIELD_DEFINITION
directive @customDirective(message: String!) on FIELD_DEFINITION
✅
directive @customDirective(message: String!) on OBJECT | FIELD_DEFINITION
directive @customDirective(message: String!) on OBJECT | FIELD_DEFINITION
INCONSISTENT_TYPE_SYSTEM_DIRECTIVE_REPEATABLE
Indicates that a type system directive definition is marked repeatable in only some of the subgraphs that declare the directive and will be repeatable in the supergraph.
❌
directive @customDirective on OBJECT
directive @customDirective repeatable on OBJECT
✅
directive @customDirective repeatable on OBJECT
directive @customDirective repeatable on OBJECT
INCONSISTENT_UNION_MEMBER
Indicates that a member of a union type definition is only defined in some of the subgraphs that declare the union.
❌
type Product {id: ID!name: String}type Service {id: ID!description: String}union SearchResult = Product | Service
type Product {id: ID!name: String}union SearchResult = Product
✅
type Product {id: ID!name: String}type Service {id: ID!description: String}union SearchResult = Product | Service
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 field with the @override directive no longer exists in a source subgraph, so the directive can be safely removed.
❌
type Product @key(fields: "id") {id: ID!inStock: Boolean! @override(from: "Subgraph B")}
type Product @key(fields: "id") {id: ID!name: String!}
✅
type Product @key(fields: "id") {id: ID!inStock: Boolean!}
type Product @key(fields: "id") {id: ID!name: String!}
OVERRIDDEN_FIELD_CAN_BE_REMOVED
Indicates that a field has been overridden by another subgraph. You should consider removing the overriden field to avoid confusion.
❌
type Product @key(fields: "id") {id: ID!name: String!inStock: Boolean! @override(from: "Subgraph B")}
type Product @key(fields: "id") {id: ID!inStock: Boolean!}
✅
type Product @key(fields: "id") {id: ID!name: String!}
type Product @key(fields: "id") {id: ID!inStock: Boolean!}
UNUSED_ENUM_TYPE
Indicates that an enum type is defined in some subgraphs but is unused (no field/argument references it). All values from subgraphs defining that enum will be included in the supergraph.
❌
enum ProductStatus {AVAILABLESOLD_OUT}type Product {id: ID!name: String}
type Order {id: ID!product: Productstatus: String}
✅
type Product {id: ID!name: Stringstatus: ProductStatus}
type Order {id: ID!product: Productstatus: ProductStatus}
Directives
DIRECTIVE_COMPOSITION
Indicates that an issue was detected when composing custom directives.
MERGED_NON_REPEATABLE_DIRECTIVE_ARGUMENTS
Indicates a non-repeatable directive has been applied to a schema element in different subgraphs with different arguments and the arguments values were merged using the directive configured strategies.
❌
type Product {id: ID!name: String}type Query {products: [Product] @customDirective(orderBy: ["name"])}
type Product {id: ID!name: String}type Query {products: [Product] @customDirective(orderBy: ["price"])}
✅
type Product {id: ID!name: String}type Query {products: [Product] @customDirective(orderBy: ["name", "price"])}
type Product {id: ID!name: String}type Query {products: [Product] @customDirective(orderBy: ["name", "price"])}
NO_EXECUTABLE_DIRECTIVE_INTERSECTION
Indicates that an executable directive definition is declared with no shared locations across subgraphs.
❌
directive @log(message: String!) on QUERY
directive @log(message: String!) on FIELD
✅
directive @log(message: String!) on QUERY | FIELD
directive @log(message: String!) on QUERY | FIELD
FROM_SUBGRAPH_DOES_NOT_EXIST
Indicates the source subgraph specified by @override
directive doesn't exist.
❌
type Product @key(fields: "id") {id: ID!inStock: Boolean! @override(from: "Subgraph B")}
# Subgraph B doesn't exist
✅
type Product @key(fields: "id") {id: ID!inStock: Boolean! @override(from: "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
The schema 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 schema must include a description.
❌
type User {username: String!}
✅
"Represents a user"type User {"The user's username"username: String!}
DEFINED_TYPES_ARE_USED
Every type defined in a schema should be used at least once.
Unused types commonly appear after you refactor other parts of your schema. You should remove unused types to prevent confusion.
❌
type SomeUnusedType { # Also fails the TYPE_SUFFIX rule!name: String!}type AnActuallyUsedType {name: String!}type Query {hello: String!title: AnActuallyUsedType}
✅
type Book {title: String!}type Query {books: [Book!]!}
QUERY_DOCUMENT_DECLARATION
A GraphQL schema should never include definitions of GraphQL operations (such as queries and mutations).
❌
type Query {users: [User!]!}query GetUsers {# Don't define operations in a schema documentusers {id}}
Directives
CONTACT_DIRECTIVE_MISSING
A subgraph schema should always provide owner contact details via the @contact
directive. Learn more.
✅
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 SCHEMAextend 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
directive should always include a reason
argument.
❌
type Product {title: String @deprecatedname: String!}
✅
type Product {title: String @deprecated(reason: "Use Product.name instead")name: String!}
TAG_DIRECTIVE_USES_UNKNOWN_NAME
The @tag
directive should always use an approved value for its name
argument. You specify approved values in GraphOS Studio.