Skip to content

Configuration

The main package composes the final flat config array from one public install: @santi020k/eslint-config-basic.

  • Start with eslintConfig().
  • Let project detection enable TypeScript, frameworks, runtime, and supported tooling.
  • Make options explicit when you want stable, reviewable config.
  • Use booleans for bundled framework configs.
  • Use enums or matching strings for integrations.
  • Use features when you want one simple opt-in/opt-out map for optional configs.
  • Use optionMergeStrategy when you want strict replace behavior.
  • Use detection for granular auto-detection control.
  • Use projects for package-aware monorepo configuration.
  • Use ignores for extra global ignore globs alongside the composed config (same as a leading flat-config object with only ignores).
  • Add local flat-config overrides as extra defineConfig() arguments instead of rebuilding the array yourself.
import { defineConfig, Extension, Format, Library, Runtime, Testing, Tool } from '@santi020k/eslint-config-basic'
export default await defineConfig({
detectRootDir: process.cwd(),
extensions: [Extension.Unicorn, Extension.Security],
formats: [Format.Markdown, Format.Mdx],
frameworks: {
react: true
},
libraries: [Library.Tailwind, Library.I18next],
optionMergeStrategy: 'merge',
runtime: Runtime.Browser,
testing: [Testing.Vitest],
tools: [Tool.Prettier],
typescript: true
})

For application packages, prefer one defineConfig() call with a stable detection root, bundled framework booleans, and first-class integration options. This keeps the generated config inspectable while still letting v2 auto-detection fill in TypeScript, runtime, testing, and supported libraries.

import { defineConfig, NextMode, Testing } from '@santi020k/eslint-config-basic'
export default await defineConfig({
detectRootDir: import.meta.dirname,
frameworks: {
next: true
},
nextMode: NextMode.AppRouter,
tailwind: {
entryPoint: 'src/app/globals.css',
ignore: ['^prose-custom$', '^icon-wrapper$'],
noUnknownClasses: 'warn'
},
testing: [Testing.Vitest],
tsconfigRootDir: import.meta.dirname,
typescript: true
})

Use detectRootDir: import.meta.dirname when a package can be linted from more than one working directory, such as from a monorepo root and from the package folder. Use tsconfigRootDir when TypeScript project service needs to resolve tsconfig.json from that same package folder.

Optional configs are grouped into five categories: extensions, formats, libraries, testing, and tools. You can enable them with enums or with the matching string values.

import { defineConfig, Library, Testing, Tool } from '@santi020k/eslint-config-basic'
export default await defineConfig({
libraries: [Library.Zod, 'tailwind'],
testing: [Testing.Playwright, 'vitest'],
tools: [Tool.Prettier, 'cspell']
})

For the simplest manual configuration, use features. Keys are the same public string names used by the enums.

import { defineConfig } from '@santi020k/eslint-config-basic'
export default await defineConfig({
features: {
'astro-doctor': true,
boundaries: true,
cspell: true,
'github-actions': true,
playwright: true,
prettier: true,
tailwind: true,
unicorn: false,
zod: true
}
})

features participates in the same merge flow as the category arrays. true enables an optional config, and false disables it even if it was detected or enabled by a preset. integrations is an alias for features.

PresetMeaning
BasicCore JavaScript rules only.
NodeCore + TypeScript + Node globals.
BrowserCore + TypeScript + Browser globals.
WorkerCore + TypeScript + worker globals.
LibraryTypeScript package/library defaults with Prettier and best-practice rules.
AppBrowser app defaults with TypeScript, Prettier, and Vitest.
CIUniversal TypeScript defaults with CI strict severities.
MonorepoMixed-workspace defaults for package-aware configs.
AllTypeScript plus all bundled integrations.

Presets do not force a framework. Frameworks come from project detection or the frameworks option.

import { defineConfig, NextMode } from '@santi020k/eslint-config-basic'
export default await defineConfig({
frameworks: {
next: true
},
nextMode: NextMode.AppRouter
})

Next.js, Expo, and Remix automatically include React rules. You can still pass imported config arrays or factories for advanced cases, but app configs should prefer booleans.

FrameworkOption
Reactframeworks.react
Next.jsframeworks.next
Astroframeworks.astro
Vueframeworks.vue
Svelteframeworks.svelte
Solidframeworks.solid
Angularframeworks.angular
NestJSframeworks.nest
Honoframeworks.hono
Expoframeworks.expo
Qwikframeworks.qwik
Remixframeworks.remix
Viteframeworks.vite
Slidevframeworks.slidev

Scalars always follow this order:

  1. Explicit options passed to eslintConfig({}).
  2. Preset defaults.
  3. Auto-detection from package.json, tsconfig.json, and project structure.

List options (libraries, testing, formats, tools, extensions) and frameworks use:

  • optionMergeStrategy: 'merge' (default): detected + preset + explicit are combined and deduplicated.
  • optionMergeStrategy: 'replace': explicit values replace preset/detected values.

features and integrations are applied to the optional-config lists too. Use true to add a config and false to remove it from the final merged set.

Use autoFrameworks: false when you want manual framework control only (no detected framework auto-enable).

Use detection: false to disable all auto-detection, or pass an object to disable specific categories while keeping the rest automatic.

import { defineConfig, Library, Testing } from '@santi020k/eslint-config-basic'
export default await defineConfig({
detection: {
formats: true,
frameworks: true,
libraries: false,
testing: false,
tools: true
},
libraries: [Library.Tailwind],
testing: [Testing.Vitest]
})

Supported detection keys are typescript, frameworks, libraries, testing, formats, tools, extensions, runtime, nextMode, and projects.

Pass ignores when you want repo-specific globs inside eslintConfig() instead of a separate array entry. Patterns behave like ESLint flat config global ignores (relative to the ESLint working directory). They are not merged from presets or auto-detection. For projects sub-configs, patterns are not rewritten with the subfolder prefix; use paths that make sense from the config file’s working directory.

import { defineConfig } from '@santi020k/eslint-config-basic'
export default await defineConfig({
ignores: ['dist/**', 'packages/*/dist/**', 'coverage/**']
})

Pass flat-config entries after the options object when a project needs a small rule exception, custom globals, or a file-specific override. The generated config stays first, and local entries are appended last.

import { defineConfig } from '@santi020k/eslint-config-basic'
export default await defineConfig(
{
frameworks: { astro: true },
typescript: true
}, {
files: ['**/*.astro'],
rules: {
'@typescript-eslint/no-unsafe-return': 'off'
}
}
)

The composed config ships a default ignore block (dist, build, coverage, framework output folders, node_modules, and similar). It also ignores common generated-code folders and files such as __generated__, generated, codegen, *.generated.*, *.gen.*, GraphQL generated output, and .prisma. AI coding-assistant artifact folders — .agent, .agents, .aider*, .claude, .clinerules, .codex, .copilot, .cursor, .gemini, .kiro, .opencode, .roo, and .windsurf — are ignored too. Disable the whole block with settings: [Setting.NoDefaultIgnores], or disable only generated-code ignores with settings: [Setting.NoGeneratedCodeIgnores].

  • detectRootDir: root used to detect dependencies, framework folders, and project files.
  • tsconfigRootDir: root passed to TypeScript parser options.

In monorepos these can differ. Example:

import { defineConfig } from '@santi020k/eslint-config-basic'
export default await defineConfig({
detectRootDir: process.cwd(),
tsconfigRootDir: new URL('.', import.meta.url).pathname
})

When a project cannot use TypeScript’s project service, keep the setting inside typescript instead of adding a manual parser override block:

import { defineConfig } from '@santi020k/eslint-config-basic'
export default await defineConfig({
typescript: {
project: true,
projectService: false
}
})

Tailwind is auto-detected when the project depends on Tailwind packages. Use tailwind when the entry point or project-specific class ignores need to be explicit:

import { defineConfig } from '@santi020k/eslint-config-basic'
export default await defineConfig({
tailwind: {
entryPoint: 'src/styles/global.css',
ignore: ['^prose-custom$', '^icon-wrapper$'],
noUnknownClasses: 'warn'
}
})

Use noUnknownClasses: false when a project uses many generated or framework-provided classes but should keep the rest of the Tailwind rules.

Relative entry points are resolved from detectRootDir, so project-scoped monorepo configs work when ESLint runs from the repository root. Set tailwind.cwd only when Tailwind must resolve from a different directory.

Set tailwind: false to disable auto-detected Tailwind linting for a package.

Testing integrations ship with default file globs. Override them only when your project stores tests somewhere unusual:

import { defineConfig, Testing } from '@santi020k/eslint-config-basic'
export default await defineConfig({
testing: [Testing.Playwright],
testingFiles: {
playwright: ['tests/**/*.ts']
}
})

Use projects to scope package-specific presets and integrations to workspace folders. With preset: Preset.Monorepo, workspace project detection is enabled by default for common workspace folders and package.json#workspaces. Outside the monorepo preset, use detection: { projects: true } to opt in.

Use projectDefaults for settings shared by most workspace packages. Arrays and option maps merge with each project by default; a project can set optionMergeStrategy: 'replace' when it needs an isolated value.

import { defineConfig, Extension, Preset, Runtime, Tool } from '@santi020k/eslint-config-basic'
export default await defineConfig({
detection: { projects: true },
preset: Preset.Monorepo,
projectDefaults: {
extensions: [Extension.Unicorn],
tools: [Tool.Prettier],
typescript: true
},
projects: {
'apps/api': {
runtime: Runtime.Node
},
'apps/web': {
frameworks: { next: true },
preset: Preset.App
}
}
})

Each project key is treated as a folder relative to the repo root. The generated project entries and inherited defaults are scoped to that folder.

import { defineConfig, Extension, Format, Library, Testing, Tool } from '@santi020k/eslint-config-basic'
export default await defineConfig({
extensions: [
Extension.Unicorn,
Extension.Sonarjs,
Extension.Perfectionist,
Extension.Security,
Extension.Regexp,
Extension.BestPractices,
Extension.Boundaries
],
formats: [
Format.Mdx,
Format.Markdown,
Format.Jsonc,
Format.Graphql,
Format.PackageJson,
Format.Yaml,
Format.Toml
],
frameworks: {
next: true,
react: true
},
libraries: [
Library.AiSdk,
Library.OpenAiAgents,
Library.Mastra,
Library.Mcp,
Library.Tailwind,
Library.TanstackQuery,
Library.TanstackRouter,
Library.Storybook,
Library.I18next,
Library.Prisma,
Library.Drizzle,
Library.Typeorm,
Library.MikroOrm,
Library.Sequelize
],
strict: true,
testing: [
Testing.Vitest,
Testing.Playwright,
Testing.TestingLibrary,
Testing.Cypress
],
tools: [
Tool.Prettier,
Tool.Cspell,
Tool.Command,
Tool.GithubActions,
Tool.Docker,
Tool.Nx,
Tool.Jsdoc,
Tool.Swagger
],
typescript: 'strict'
})

Import sorting is enabled by default via simple-import-sort. The built-in groups handle Node built-ins, framework virtual modules (Vite, Astro, SvelteKit, Nuxt), internal UI and app layers, styles, external npm packages, and relative imports — all without any configuration.

For monorepos where you want your own workspace packages to sort in a dedicated block before external npm packages, pass workspacePrefixes to defineConfig:

import { defineConfig } from '@santi020k/eslint-config-basic'
export default await defineConfig({
workspacePrefixes: ['@acme'] // @acme/* sorts before react, lodash, etc.
})

See the Core Package page for the full group order and more details.

import { defineConfig, Library } from '@santi020k/eslint-config-basic'
export default await defineConfig({
frameworks: { remix: true },
libraries: [Library.Tailwind]
})
import { defineConfig, Testing } from '@santi020k/eslint-config-basic'
export default await defineConfig({
frameworks: { astro: true, svelte: true },
testing: [Testing.Vitest]
})
import { defineConfig } from '@santi020k/eslint-config-basic'
export default await defineConfig({
strict: true
})

Strict mode accepts profiles:

ValueBehavior
false or 'recommended'Keep recommended rule severities.
true or 'ci'Promote warnings to errors.
'pedantic'Promote warnings and enable built-in best-practice rules.

Gitignore integration is enabled by default.

import { defineConfig, Setting } from '@santi020k/eslint-config-basic'
export default await defineConfig({
settings: [Setting.NoGitignore]
})

This repo can generate a JSON schema for EslintConfigOptions:

Terminal window
pnpm run build:schema

The output file is eslint-config-schema.json at the repository root.

Did this page help?

One click helps us spot documentation that needs another pass. No personal data is sent.