A fast, opinionated linter for GraphQL SDL (Schema Definition Language) with first-class Apollo Federation support.
graphql-linter validates your .graphql and .graphqls files for syntax
errors, schema design best practices, and correct usage of Apollo Federation
directives — all from a single, dependency-free binary.
- Why GraphQL Linter?
- Features
- Installation
- Quick start
- Usage
- Configuration
- Rules
- Suppressing findings
- Pre-commit hook
- Development
- Contributing
- License
The widely used graphql-schema-linter
does not support Apollo Federation, and the
request to add it
has been open since 2020.
graphql-linter fills that gap. It honours the graphql-schema-linter rule set
that teams already rely on and adds validation for Apollo Federation directives
and composition on top. Because it ships as a single static Go binary, there is
no Node.js toolchain to install and it drops cleanly into CI pipelines and
pre-commit hooks.
- Drop-in rule parity — implements the rules from
graphql-schema-linter. - Apollo Federation aware — recognizes and validates federation directives
(
@key,@external,@requires,@provides,@shareable,@override,@inaccessible,@tag, and more) and flags invalid directives or typos. - Schema hygiene checks — enforces descriptions, naming conventions, alphabetical sorting, deprecation reasons, and Relay connection specs.
- Clear diagnostics — reports the rule, file, line number, and context for every finding.
- Flexible suppressions — silence specific findings per file, line, and rule through a config file.
- Single binary — no runtime dependencies; runs anywhere Go binaries run.
ARCH=$(uname -m | awk '{if ($1=="x86_64") print "amd64"; else if ($1=="arm64" || $1=="aarch64") print "arm64"; else { print "Unsupported architecture: " $1 > "/dev/stderr"; exit 1 }}')
OS=$(uname | tr '[:upper:]' '[:lower:]')
VERSION=v0.1.0
curl --fail -L "https://github.com/schubergphilis/graphql-linter/releases/download/${VERSION}/graphql-linter-${VERSION}-${OS}-${ARCH}" \
-o graphql-linter && \
chmod +x graphql-linter && \
./graphql-linter --version | grep "${VERSION}"Pre-built binaries are published for linux/amd64, linux/arm64, and
darwin/arm64. See the releases page
for all available builds.
go install github.com/schubergphilis/graphql-linter/cmd/graphql-linter@v0.1.0 && \
graphql-linter --versiongit clone https://github.com/schubergphilis/graphql-linter.git
cd graphql-linter
go build -o graphql-linter ./cmd/graphql-linterLint every GraphQL file in a directory:
graphql-linter -targetPath ./schemaLint a single file:
graphql-linter -targetPath ./schema/user.graphqlsThe linter walks the target path recursively, skipping node_modules,
vendor, .git, and any dot-directory. It exits non-zero when unsuppressed
findings are detected, making it CI-ready out of the box.
graphql-linter [flags]
| Flag | Description |
|---|---|
-targetPath |
Directory or file containing the GraphQL schemas to check. Defaults to the project root. |
-configPath |
Path to the configuration file. Defaults to .graphql-linter.yml in the project root. |
-verbose |
Enable verbose output. |
-version |
Print version information and exit. |
# Lint with a custom configuration file
graphql-linter -configPath ./config/.graphql-linter.yml -targetPath ./schema
# Verbose run
graphql-linter -targetPath ./schema -verbose
# Show help
graphql-linter --helpWhen running from a checkout of this repository you can invoke the linter
directly with go run:
go run ./cmd/graphql-linter -targetPath test/testdata/graphql/base/invalidWhen -configPath is not set, the linter looks for a .graphql-linter.yml file
in the project root. Use -configPath to point at a different file. If no
configuration is found, the built-in defaults below are used.
---
# Global behaviour
settings:
# Treat warnings as errors.
strictMode: true
# Validate Apollo Federation directives.
validateFederation: true
# Require descriptions on schema elements.
checkDescriptions: true
# Findings to silence (see "Suppressing findings" below)
suppressions:
- file: schema/user.graphqls
line: 42
rule: types-have-descriptions
value: User
reason: Documented in the federation gateway instead.A fully commented reference configuration is available in .graphql-linter.yml.example.
| Setting | Default | Description |
|---|---|---|
strictMode |
true |
Treat warnings as errors. |
validateFederation |
true |
Validate Apollo Federation directives. |
checkDescriptions |
true |
Require descriptions on types, fields, and enums. |
These mirror the graphql-schema-linter rule set:
arguments-have-descriptionsdefined-types-are-useddeprecations-have-a-reasondescriptions-are-capitalizedenum-values-all-capsenum-values-have-descriptionsenum-values-sorted-alphabeticallyfields-are-camel-casedfields-have-descriptionsinput-object-fields-sorted-alphabeticallyinput-object-values-are-camel-casedinput-object-values-have-descriptionsinterface-fields-sorted-alphabeticallyrelay-connection-types-specrelay-connection-arguments-specrelay-page-info-spectype-fields-sorted-alphabeticallytypes-are-capitalizedtypes-have-descriptions
When validateFederation is enabled, the linter also verifies Apollo Federation
usage, including:
- Only valid federation directives are used on types and fields
(
@key,@external,@requires,@provides,@extends,@shareable,@inaccessible,@override,@composeDirective,@interfaceObject,@tag,@deprecated,@specifiedBy,@oneOf). - Directive typos are detected and closest-match suggestions are offered.
- Composition-level validation of federated types.
Individual findings can be suppressed in the configuration file. Every field is
optional and acts as a filter: an omitted field matches anything, so narrow the
suppression by combining fields. Always include a reason for auditability,
even though it is not enforced.
suppressions:
- file: test/testdata/graphql/base/invalid/07-enum-values-sorted-alphabetically.graphql
line: 12
rule: defined-types-are-used
value: PageInfo
reason: PageInfo is intentionally unused in this test schema.| Field | Matching behaviour |
|---|---|
file |
Matches when the schema path ends with this value; omit to match any file. |
line |
Matches this line number; omit (or 0) to match any line. |
rule |
Matches this rule identifier; omit to match any rule. |
value |
Matches a specific symbol (type, field, enum value); omit to match any value. |
reason |
Free-form justification for the suppression (recommended, not enforced). |
graphql-linter ships a pre-commit hook so schemas
are linted automatically before every commit.
Add the following to the .pre-commit-config.yaml in your repository:
repos:
- repo: https://github.com/schubergphilis/graphql-linter
# Replace with the latest released tag; run `pre-commit autoupdate` to bump.
rev: v0.1.4
hooks:
- id: graphql-linterThen install and run it:
pre-commit install
pre-commit run graphql-linter --all-filesThe hook is triggered whenever a .graphql or .graphqls file is staged. It
lints the whole project (so cross-file Apollo Federation composition is
validated) and fails the commit when linting errors are found.
Configuration and suppressions are picked up from the .graphql-linter.yml
file in the repository root, as described above.
This project follows a Clean Architecture layout (presentation → application → data) and uses Task for common workflows.
# Run the full test suite
task remote:test
# Run integration and component tests
task remote:test-integration
task remote:test-component
# Lint and format
task remote:lint
task remote:format
task remote:fix-linting-issues
# Regenerate mocks (mockery)
task remote:mock-generate
# Regenerate test data fixtures
go run ./cmd/graphql-testdata-generatorcmd/
graphql-linter/ CLI entry point
graphql-testdata-generator/ Test fixture generator
internal/
app/graphql-linter/
presentation/ CLI parsing and I/O
application/ Linting orchestration and reporting
data/ Config, schema parsing, rule execution
base/rules/ Schema rules
federation/rules/ Apollo Federation rules
pkg/ Shared helpers and constants
test/ Component tests and GraphQL fixtures
Contributions are welcome! To propose a change:
- Fork the repository and create a feature branch.
- Add or update tests for your change.
- Ensure
task remote:testandtask remote:lintpass. - Open a pull request describing the motivation and behaviour change.
Please keep pull requests focused and include test coverage for new rules or fixes.
Released under the MIT License. Copyright (c) 2025 Schuberg Philis.
