Skip to content

ESLint Plugin

@graphql-analyzer/eslint-plugin runs the same lint rules as the CLI and LSP, but inside ESLint. It’s a drop-in replacement for @graphql-eslint/eslint-plugin — same plugin names, same rule names, same flat-config preset names. The Rust analyzer does the real work via a native addon, so performance matches the CLI.

  • Node.js 18 or later
  • ESLint 8.40+ or 9.x (flat config only)
Terminal window
npm install --save-dev @graphql-analyzer/eslint-plugin

During the alpha, install with the alpha dist-tag:

Terminal window
npm install --save-dev @graphql-analyzer/eslint-plugin@alpha

The native addon is distributed as platform-specific optional dependencies (darwin-arm64, darwin-x64, linux-x64-gnu, linux-arm64-gnu, win32-x64-msvc). npm installs the one matching your machine automatically.

eslint.config.mjs
import graphql from "@graphql-analyzer/eslint-plugin";
export default [
// 1. Lint `.graphql` files directly. This block also handles the virtual
// `.graphql` blocks the processor extracts from JS/TS/SFC hosts (block
// paths look like `path/to/component.tsx/0_document.graphql`).
{
files: ["**/*.graphql"],
languageOptions: {
parser: graphql.parser,
},
plugins: {
"@graphql-analyzer": graphql,
},
rules: {
"@graphql-analyzer/no-anonymous-operations": "error",
"@graphql-analyzer/no-duplicate-fields": "error",
"@graphql-analyzer/no-hashtag-description": "warn",
},
},
// 2. Wire the named processor onto every host extension that may carry
// embedded GraphQL. Vue, Svelte, and Astro files are supported by the
// native extractor — list their extensions here alongside JS/TS so the
// processor sees them.
{
files: ["**/*.{js,jsx,mjs,cjs,ts,tsx,vue,svelte,astro}"],
plugins: {
"@graphql-analyzer": graphql,
},
processor: "@graphql-analyzer/graphql",
},
];

The native addon detects embedded GraphQL in JavaScript, TypeScript, Vue single-file components, Svelte components, and Astro components. Diagnostics are reported at their original source position so gql tagged templates don’t need a separate file.

ExtensionWhat gets scannedHost parser you’ll likely want
.js, .jsx, .mjs, .cjsThe whole fileNone (espree, ESLint’s default, handles it)
.ts, .tsxThe whole file@typescript-eslint/parser
.vueAll <script> and <script setup> blocksvue-eslint-parser
.svelteAll <script> blocks (including context="module")svelte-eslint-parser
.astroThe frontmatter (between --- fences)astro-eslint-parser

The lang attribute on <script> tags is honored — lang="ts" is parsed as TypeScript, anything else as JavaScript. Astro frontmatter is always TypeScript per Astro’s own conventions.

ESLint’s default parser (espree) only understands JavaScript. Without a host parser configured, ESLint reports a fatal “Parsing error” on the host file itself, which sits alongside the GraphQL diagnostics from the processor. The embedded GraphQL is still linted — but you’ll usually want a real host parser so the rest of your ESLint config (TypeScript rules, framework rules, etc.) can run too. Add one in a separate config block per host:

import graphql from "@graphql-analyzer/eslint-plugin";
import tsParser from "@typescript-eslint/parser";
import vueParser from "vue-eslint-parser";
import svelteParser from "svelte-eslint-parser";
import astroParser from "astro-eslint-parser";
export default [
// GraphQL block + processor block (see "Usage" above).
/* ... */
// Host parsers — match each extension to the parser that understands it.
// The processor still does the embedded extraction; these blocks just give
// ESLint a working host AST so the rest of your config has something to
// run against.
{
files: ["**/*.{ts,tsx}"],
languageOptions: { parser: tsParser },
},
{
files: ["**/*.vue"],
languageOptions: {
parser: vueParser,
parserOptions: { parser: tsParser },
},
},
{
files: ["**/*.svelte"],
languageOptions: {
parser: svelteParser,
parserOptions: { parser: tsParser },
},
},
{
files: ["**/*.astro"],
languageOptions: { parser: astroParser },
},
];

We intentionally don’t bundle these parsers — pick the ones that already match the rest of your project’s ESLint config.

Rules pick up their configuration from .graphqlrc.yaml (or any supported graphql-config file) in the nearest ancestor directory of the file being linted:

.graphqlrc.yaml
schema: "schema.graphql"
documents: "src/**/*.graphql"
extensions:
graphql-analyzer:
lint:
rules:
noHashtagDescription: warn
noAnonymousOperations: error
noDuplicateFields: error

This is the same config format the CLI and LSP use, so your editor, CI, and ESLint all see the same rules.

import graphql from "@graphql-analyzer/eslint-plugin";
export default [
{
files: ["**/*.graphql"],
languageOptions: { parser: graphql.parser },
plugins: { "@graphql-analyzer": graphql },
rules: graphql.configs["flat/schema-recommended"].rules,
},
];

Available presets:

  • flat/schema-recommended — sensible defaults for schema files
  • flat/operations-recommended — sensible defaults for operation documents
  • flat/schema-all — all schema rules enabled
  • flat/schema-relay — Relay-specific schema rules
  • flat/operations-all — all operation rules enabled

Migrating from @graphql-eslint/eslint-plugin

Section titled “Migrating from @graphql-eslint/eslint-plugin”

The migration is a find-and-replace:

  1. @graphql-eslint/eslint-plugin@graphql-analyzer/eslint-plugin
  2. @graphql-eslint@graphql-analyzer (plugin names, rule prefixes)

Rule names, rule options, and preset names are identical.

import graphqlPlugin from "@graphql-eslint/eslint-plugin";
import graphqlPlugin from "@graphql-analyzer/eslint-plugin";
export default [
{
files: ["**/*.graphql"],
languageOptions: { parser: graphqlPlugin.parser },
plugins: { "@graphql-eslint": graphqlPlugin },
plugins: { "@graphql-analyzer": graphqlPlugin },
rules: {
"@graphql-eslint/no-anonymous-operations": "error",
"@graphql-analyzer/no-anonymous-operations": "error",
},
},
];

Rule parity with @graphql-eslint/eslint-plugin

Section titled “Rule parity with @graphql-eslint/eslint-plugin”

The plugin’s test suite diffs its rule set against @graphql-eslint/eslint-plugin and fails CI on unexpected divergence — migration stays a find-and-replace. Current intentional gaps:

  • Validation rules (from graphql-js’s specifiedRules: known-type-names, fields-on-correct-type, no-undefined-variables, etc.) aren’t exposed as lint rules. They run as part of the analyzer’s validation pass instead.

See packages/eslint-plugin/test/parity.test.mjs for the full allowlist.

  • ESLint legacy config (.eslintrc) is not supported — flat config only.
  • Autofix coverage matches upstream: alphabetize only. Other shared rules ship suggest (suggestions) upstream and are not yet wired through our ESLint shim.

See the rules catalog for the full list of rules available.