Skip to content

Getting started

All eleven ESLint plugins share one shape. Requirements: ESLint 9 or newer, flat config only (eslint.config.js), Node 22 or newer. TypeScript sources are parsed with @typescript-eslint/parser, which you almost certainly have already.

Start with the three plugins that fit almost any TypeScript codebase, then add the ones for the stack you use:

Add When
code-quality, async-safety, contracts Always: the starter set
security You run server code (shell, HTTP, redirects, HTML rendering)
react You write React components
rsc You use the Next.js App Router / React Server Components
prisma You use Prisma (several rules need your tenant models)
llm You call an LLM SDK
observability You log through a structured logger
architecture, monorepo You want folder and package boundaries enforced (monorepo needs your scope)

Each preset is its own config entry with the same files and parser. They do not merge, and the plugin namespaces (noctcore-<plugin>) never collide:

eslint.config.js
import tsParser from '@typescript-eslint/parser';
import asyncSafety from '@noctcore/eslint-plugin-async-safety';
import codeQuality from '@noctcore/eslint-plugin-code-quality';
import contracts from '@noctcore/eslint-plugin-contracts';
import react from '@noctcore/eslint-plugin-react';
import security from '@noctcore/eslint-plugin-security';
const typescript = { files: ['**/*.{ts,tsx}'], languageOptions: { parser: tsParser } };
export default [
// The starter set.
{ ...codeQuality.configs.recommended, ...typescript },
{ ...asyncSafety.configs.recommended, ...typescript },
{ ...contracts.configs.recommended, ...typescript },
// Add the ones for your stack.
{ ...security.configs.recommended, ...typescript },
{ ...react.configs.recommended, ...typescript },
];

The steps below take one plugin at a time and explain each piece.

Terminal window
npm install --save-dev @noctcore/eslint-plugin-react
# bun add -D / pnpm add -D work the same way

Every plugin exports configs.recommended, a complete flat-config block that registers the plugin under its namespace (noctcore-<plugin>) and sets each rule’s severity.

eslint.config.js
import react from '@noctcore/eslint-plugin-react';
import asyncSafety from '@noctcore/eslint-plugin-async-safety';
export default [
// ...your existing config
react.configs.recommended,
asyncSafety.configs.recommended,
];

configs.recommended registers the plugin and sets rule severities, nothing else. It has no files pattern, so it applies to whatever files the rest of your config lints, and no parser, so on its own ESLint cannot read TypeScript. For .ts and .tsx files, give the preset both:

eslint.config.js
import tsParser from '@typescript-eslint/parser';
import react from '@noctcore/eslint-plugin-react';
export default [
{
...react.configs.recommended,
files: ['**/*.{ts,tsx}'],
languageOptions: { parser: tsParser },
},
];

If your config already sets the parser for those files (through typescript-eslint’s configs, for example), spreading the preset alongside it is enough.

3. Turn on the rules that need your project’s knowledge

Section titled “3. Turn on the rules that need your project’s knowledge”

Some rules cannot do anything useful until they know something about your codebase: your workspace scope, your tenant-scoped models, your translation catalogs. Those ship off or are left out of the preset, because a rule that reports nothing reads as coverage you do not have. Each package page’s rule table shows which ones, and each rule page documents its options.

export default [
monorepo.configs.recommended,
{
rules: {
'noctcore-monorepo/no-deep-package-imports': ['error', { scopes: ['@acme'] }],
},
},
];

noctcore-prisma/mutation-entry-must-reach-audit follows calls across files, so it needs a type-checked program. Linting it without one is an error, not a silent pass. One more rule, noctcore-contracts/translation-key-exists, runs without type information and uses it when present (to resolve a namespace from an identifier’s type). Every other rule is syntactic. The Types column of each package’s rule table, and the all-rules index, say which is which.

export default [
{
languageOptions: { parserOptions: { projectService: true } },
},
];

Every rule in every preset is error or off. A warning is a rule nobody obeys: if a rule matters it fails the build, and if it does not fit your codebase you switch it off. The no-warn-severity lint-meta rule exists to hold a repo to the same policy. Turning a preset on in a codebase that already has hundreds of hits is its own page: adopting in an existing codebase.

Every rule’s meta.docs.url points at its page on this site, so the link your editor or CI shows next to a report lands on the rendered rule doc.