Skip to content

Migrating from @graphql-eslint/eslint-plugin

@graphql-analyzer/eslint-plugin is designed as a drop-in replacement for @graphql-eslint/eslint-plugin. Plugin name, rule names, message text, source positions, and messageId values all match the upstream plugin where the rule is shared. CI enforces this with a parity test that fails on any unexplained drift.

This page documents what to change in your project, what is structurally identical, and what behaviors still diverge during the alpha.

For most projects, migration is two find-and-replace passes:

  1. @graphql-eslint/eslint-plugin@graphql-analyzer/eslint-plugin (in package.json and your ESLint config imports).
  2. The plugin object you bind in flat config keeps the same @graphql-eslint key by convention — but you may switch it to @graphql-analyzer to match the package name. Rule names below the prefix are unchanged.

If you don’t customize message text or messageIds, nothing else needs to change.

  • Rule names. All 31 shared rules use the same kebab-case name as upstream. Migration to a new prefix is mechanical.
  • Recommended preset names. flat/schema-recommended and flat/operations-recommended are exposed under the configs export.
  • Rule options. Options pass through unchanged — anything the upstream rule accepted, this plugin accepts (and ignores unknown keys rather than rejecting them, for forward compatibility). Options can come from either ESLint’s rules: { rule: [severity, options] } config or the extensions.graphql-analyzer.lint.rules block in .graphqlrc.yaml; ESLint takes precedence per-rule when both are present.
  • .graphqlrc.yaml resolution. Both plugins read schema/documents from the same graphql-config file.
  • Diagnostic shape. LintMessage.message, messageId, line, column, endLine, endColumn, and fix are populated to match upstream per-diagnostic-site. The parity test pairs every diagnostic by source position and fails on any divergence.

Three rules were renamed during the alpha so all shared rules are 1:1 by name. If you were using these earlier names, update your config:

Old name (graphql-analyzer alpha)New name (matches graphql-eslint)
@graphql-analyzer/unused-fields@graphql-analyzer/no-unused-fields
@graphql-analyzer/unused-fragments@graphql-analyzer/no-unused-fragments
@graphql-analyzer/unused-variables@graphql-analyzer/no-unused-variables

In .graphqlrc.yaml lint config, the camelCase keys are now noUnusedFields, noUnusedFragments, noUnusedVariables.

Validation rules: handled, accepted, but not configurable

Section titled “Validation rules: handled, accepted, but not configurable”

@graphql-eslint exposes 30+ rules that are direct re-exports of graphql-js’s specifiedRules (known-type-names, fields-on-correct-type, no-undefined-variables, etc.). The same checks always run inside graphql-analyzer’s validation pass, so:

"@graphql-eslint/known-type-names": "error",
"@graphql-eslint/fields-on-correct-type": "error",
"@graphql-eslint/no-undefined-variables": "error",

…can stay in your config when you migrate. The plugin exposes each of those names as a no-op stub so your config loads cleanly — the configurable entry doesn’t itself emit anything, but the underlying check still runs and fires diagnostics regardless. The full stub list lives in packages/eslint-plugin/test/parity.test.mjs (STUB_RULES).

A handful of rules ship under @graphql-analyzer that don’t exist in graphql-eslint. Adding them is optional; they don’t conflict with existing configs:

  • operation-name-suffix — operation names should end in Query/Mutation/Subscription.
  • redundant-fields — flags fields that are already covered by a sibling fragment spread.
  • require-selections — enforces required field selections (e.g. id, __typename) on object types.
  • unique-names — enforces project-wide uniqueness of operation/fragment names (useful for persisted-queries setups).
  • ESLint legacy config (.eslintrc.*) is not supported. Flat config only.
  • Autofix coverage matches upstream. Both plugins ship fix only on alphabetize. Upstream additionally ships suggest (ESLint suggestions) on a number of rules; those have not yet been wired through our shim and surface as plain diagnostics here. Open an issue if a specific rule’s suggestion is blocking your migration.

The plugin ships a parity test that runs @graphql-analyzer/eslint-plugin and @graphql-eslint/eslint-plugin against the same fixture per shared rule and asserts:

  • Same diagnostic count.
  • Same message text.
  • Same messageId.
  • Same line / column / endLine / endColumn.
  • Same fix (or both null).

If you migrate and see different results, that’s a parity bug — please open an issue.

In addition to the live integration parity test (packages/eslint-plugin/test/parity.test.mjs, which runs both plugins side by side), every shared rule has its valid:/invalid: cases ported verbatim from upstream’s unit tests into Rust tests under crates/linter/src/rules/upstream/. Each ported case carries a permalink to the original upstream source line at a pinned SHA, and runs against the rule implementation directly — failures point at a specific upstream test the rule no longer matches.