@sanity-labs/eslint-plugin
v0.4.1
Published
ESLint plugin for Sanity Lint - lint GROQ queries and schema definitions
Downloads
2,702
Readme
@sanity-labs/eslint-plugin
🧪 Sanity Labs - This project is functional and actively used, but maintained on a best-effort basis. It's pre-v1, so APIs may evolve based on feedback. Issues and contributions welcome!
Catch bugs in your GROQ queries and schema definitions before they hit production.
Works with both ESLint and OxLint.
What It Catches
GROQ queries:
// ❌ Performance issue - joins in filters cause full scans
const query = groq`*[_type == "post" && author->name == "John"]`
// ⚠️ sanity/groq-join-in-filter
// ❌ Typo that silently returns nothing (with schema-aware linting)
const query = groq`*[_type == "psot"]`
// ⚠️ sanity/groq-invalid-type-filter: Type "psot" not found in schemaSchema definitions:
// ❌ Missing defineType wrapper
export const post = {
name: 'post',
type: 'document',
fields: [...]
}
// ⚠️ sanity/schema-missing-define-type
// ❌ Reserved field name that would break at runtime
defineField({ name: '_type', type: 'string' })
// ⚠️ sanity/schema-reserved-field-nameInstallation
npm install @sanity-labs/eslint-pluginUsage with ESLint
// eslint.config.js
import sanity from '@sanity-labs/eslint-plugin'
export default [
...sanity.configs.recommended,
// or for stricter checking:
// ...sanity.configs.strict,
]Usage with OxLint
OxLint supports ESLint-compatible JS plugins. Our plugin works out of the box:
// oxlint.config.json
{
"jsPlugins": ["@sanity-labs/eslint-plugin"],
"rules": {
"sanity/groq-join-in-filter": "error",
"sanity/groq-deep-pagination": "warn"
}
}Then run:
oxlint --config oxlint.config.json src/Note: OxLint JS plugins are experimental. See OxLint JS Plugins for details.
Configurations
recommended
Balanced defaults - errors for serious issues, warnings for improvements. Includes both GROQ and schema rules.
export default [...sanity.configs.recommended]strict
All rules enabled as errors.
groq
GROQ rules only - use in frontends, functions, or anywhere you write GROQ queries but don't have schema definitions.
export default [...sanity.configs.groq]schema
Schema rules only - use in schema packages within monorepos where you only want to lint schema definitions.
export default [...sanity.configs.schema]performance
Performance-focused GROQ rules only - for teams who want to catch expensive queries but don't care about style/best practices.
export default [...sanity.configs.performance]Rules
GROQ Rules
These rules lint GROQ queries in:
groq\...`` tagged template literalsdefineQuery('...')function calls (fromnext-sanityor@sanity/client)
| Rule | Default | Description |
| ------------------------------------------ | ------- | ----------------------------------------------- |
| sanity/groq-join-in-filter | error | Avoid -> inside filters |
| sanity/groq-join-to-get-id | warn | Use ._ref instead of ->_id |
| sanity/groq-deep-pagination | warn | Avoid large offsets (>=1000) |
| sanity/groq-large-pages | warn | Avoid fetching >100 results |
| sanity/groq-many-joins | warn | Avoid >10 joins in one query |
| sanity/groq-computed-value-in-filter | error | Avoid computed values in filters |
| sanity/groq-non-literal-comparison | error | Avoid comparing two non-literals |
| sanity/groq-order-on-expr | error | Avoid ordering on computed values |
| sanity/groq-repeated-dereference | info | Avoid repeated -> on same attribute |
| sanity/groq-match-on-id | info | Avoid match on _id with wildcard |
| sanity/groq-count-in-correlated-subquery | info | Avoid count() on correlated subqueries |
| sanity/groq-very-large-query | error | Query exceeds 10KB |
| sanity/groq-extremely-large-query | error | Query exceeds 100KB |
| sanity/groq-unknown-field | error | Field doesn't exist in schema (requires schema) |
| sanity/groq-invalid-type-filter | error | Type doesn't exist in schema (requires schema) |
Schema Rules
These rules lint Sanity schema definitions using defineType() and defineField().
| Rule | Default | Description |
| ------------------------------------------- | ------- | ------------------------------------------- |
| sanity/schema-missing-define-type | error | Must use defineType() |
| sanity/schema-missing-define-field | error | Fields should use defineField() |
| sanity/schema-reserved-field-name | error | Avoid reserved field names (_id, _type) |
| sanity/schema-missing-icon | warn | Document types should have icons |
| sanity/schema-missing-title | warn | Types should have titles |
| sanity/schema-missing-slug-source | warn | Slug fields need options.source |
| sanity/schema-heading-level-in-schema | warn | Don't store heading levels |
| sanity/schema-missing-description | off | Fields should have descriptions |
| sanity/schema-boolean-instead-of-list | off | Consider options.list over boolean |
| sanity/schema-array-missing-constraints | off | Arrays should have constraints |
| sanity/schema-unnecessary-reference | off | Consider embedding instead |
| sanity/schema-presentation-field-name | off | Avoid presentation-focused names |
| sanity/schema-missing-required-validation | off | Critical fields need validation |
Schema-Aware Linting
For schema-aware rules (unknown-field, invalid-type-filter), you need to provide a schema:
// eslint.config.js
import sanity from '@sanity-labs/eslint-plugin'
export default [
...sanity.configs.recommended,
{
settings: {
sanity: {
schemaPath: './schema.json',
},
},
},
]Generate schema.json with:
npx sanity schema extractMonorepo Setup
When using @sanity-labs/eslint-plugin in a monorepo (turborepo, pnpm workspaces, etc.), VS Code/Cursor may have trouble finding the ESLint config for nested packages.
VS Code / Cursor Settings
Create .vscode/settings.json at your monorepo root:
{
"eslint.workingDirectories": [
{ "directory": "apps/web", "changeProcessCWD": true },
{ "directory": "apps/studio", "changeProcessCWD": true }
]
}Replace the paths with your actual package directories that have ESLint configs.
Alternative: Auto-detect
You can also let ESLint auto-detect working directories:
{
"eslint.workingDirectories": [{ "mode": "auto" }]
}Troubleshooting
If rules still don't appear in the editor:
- Restart ESLint Server:
Cmd+Shift+P→ "ESLint: Restart ESLint Server" - Check ESLint Output: View → Output → select "ESLint" to see errors
- Verify flat config: Ensure
eslint.config.mjsexists in your package directory
Note: The CLI (
npx eslint .) works regardless of these settings. This is purely for editor integration.
License
MIT
