Configure Biome for JavaScript & TypeScript in VS Code
Replace ESLint and Prettier with Biome in a JavaScript, TypeScript, React, or Next.js project — a working biome.json, the VS Code extension settings, format on save, and a `biome ci` step for CI.
My previous lint/format setup was ESLint + Prettier + eslint-config-prettier + a half-dozen plugins, and every Next.js upgrade made one of them angry. Switching to Biome cut the config to one file and the install to one package.
This is the biome.json, VS Code wiring, and CI command I use on JavaScript, TypeScript, React, and Next.js projects.
Why Biome
The case for Biome over ESLint + Prettier is short:
- One binary, written in Rust, fast enough that you can run it on save without noticing.
- One config file (
biome.json) covering formatting, linting, import sorting, and code fixes. - Built-in support for JS, TS, JSX, TSX, JSON, and CSS — no plugin chase for new file types.
- A
biome cimode designed for CI, with non-zero exit codes and machine-readable output.
The cost is that Biome’s rule coverage is narrower than ESLint’s plugin ecosystem. For most React / Next.js work I haven’t missed anything.
Install Biome
Biome is best installed as a development dependency in your project.
npm
npm i -D -E @biomejs/biome
pnpm
pnpm add -D -E @biomejs/biome
bun
bun add -D -E @biomejs/biome
yarn
yarn add -D -E @biomejs/biome
The -E flag pins the exact Biome version, which helps keep formatting consistent across your team.
Initialize Biome
After installing Biome, generate a configuration file:
npx @biomejs/biome init
For pnpm:
pnpx @biomejs/biome init
For bun:
bunx --bun @biomejs/biome init
For yarn:
yarn exec biome -- init
This creates a biome.json file in your project.
Biome also supports biome.jsonc if you prefer comments in your configuration.
Configure biome.json
Your biome.json file is the main configuration file for Biome.
Below is a practical setup for JavaScript, TypeScript, React, Next.js, JSON, and CSS projects.
{
"$schema": "https://biomejs.dev/schemas/2.4.13/schema.json",
"vcs": {
"enabled": true,
"clientKind": "git",
"useIgnoreFile": true,
"defaultBranch": "main"
},
"files": {
"ignoreUnknown": true,
"includes": [
"**/*.js",
"**/*.jsx",
"**/*.ts",
"**/*.tsx",
"**/*.json",
"**/*.jsonc",
"**/*.css",
"**/*.scss"
],
"experimentalScannerIgnores": [
"**/node_modules/**",
"**/.next/**",
"**/dist/**",
"**/build/**",
"**/coverage/**",
"**/.turbo/**",
"**/out/**",
"**/.vercel/**",
"**/storybook-static/**",
"**/.env*",
"**/public/**",
"**/*.d.ts"
]
},
"formatter": {
"enabled": true,
"formatWithErrors": false,
"indentStyle": "space",
"indentWidth": 2,
"lineWidth": 100,
"lineEnding": "lf",
"attributePosition": "auto"
},
"linter": {
"enabled": true,
"rules": {
"recommended": true,
"style": {
"noDefaultExport": "off",
"noImplicitBoolean": "off",
"noInferrableTypes": "error",
"noNamespace": "error",
"noNegationElse": "warn",
"noNonNullAssertion": "error",
"noParameterAssign": "error",
"noUnusedTemplateLiteral": "error",
"noUselessElse": "warn",
"useBlockStatements": "error",
"useCollapsedElseIf": "error",
"useConst": "error",
"useDefaultParameterLast": "error",
"useEnumInitializers": "error",
"useExportType": "error",
"useFilenamingConvention": {
"level": "error",
"options": {
"strictCase": false,
"requireAscii": true,
"filenameCases": ["kebab-case"]
}
},
"useForOf": "error",
"useFragmentSyntax": "error",
"useImportType": "error",
"useNamingConvention": {
"level": "error",
"options": {
"strictCase": false,
"conventions": [
{
"selector": {
"kind": "function"
},
"formats": ["camelCase", "PascalCase"]
},
{
"selector": {
"kind": "variable"
},
"formats": ["camelCase", "PascalCase", "CONSTANT_CASE"]
},
{
"selector": {
"kind": "typeLike"
},
"formats": ["PascalCase"]
}
]
}
},
"useNodejsImportProtocol": "error",
"useNumberNamespace": "error",
"useSelfClosingElements": "error",
"useShorthandAssign": "error",
"useShorthandFunctionType": "error",
"useSingleVarDeclarator": "error",
"useTemplate": "error",
"useThrowOnlyError": "error"
},
"suspicious": {
"noExplicitAny": "error",
"noDebugger": "error",
"noDuplicateJsxProps": "error",
"noDuplicateObjectKeys": "error",
"noDuplicateParameters": "error",
"noShadowRestrictedNames": "error",
"noSparseArray": "error",
"noUnsafeNegation": "error",
"noArrayIndexKey": "warn",
"noAssignInExpressions": "error",
"noCatchAssign": "error",
"noClassAssign": "error",
"noCommentText": "error",
"noCompareNegZero": "error",
"noConsole": "warn",
"noConstEnum": "error",
"noControlCharactersInRegex": "error",
"noDoubleEquals": "error",
"noDuplicateCase": "error",
"noEmptyBlockStatements": "error",
"noFallthroughSwitchClause": "error",
"noFunctionAssign": "error",
"noGlobalAssign": "error",
"noLabelVar": "error",
"noMisleadingCharacterClass": "error",
"noPrototypeBuiltins": "error",
"noRedeclare": "error",
"noSelfCompare": "error",
"noUnknownAtRules": "off"
},
"correctness": {
"noConstAssign": "error",
"noConstructorReturn": "error",
"noEmptyPattern": "error",
"noInvalidConstructorSuper": "error",
"noInvalidUseBeforeDeclaration": "error",
"noSelfAssign": "error",
"noSetterReturn": "error",
"noSwitchDeclarations": "error",
"noUnreachable": "error",
"noUnreachableSuper": "error",
"noUnsafeFinally": "error",
"noUnsafeOptionalChaining": "error",
"noUnusedLabels": "error",
"noUnusedVariables": "error",
"useExhaustiveDependencies": "error",
"useHookAtTopLevel": "error",
"useIsNan": "error",
"useJsxKeyInIterable": "error",
"useValidForDirection": "error",
"useYield": "error"
},
"complexity": {
"noBannedTypes": "error",
"noExcessiveCognitiveComplexity": {
"level": "warn",
"options": {
"maxAllowedComplexity": 15
}
},
"noForEach": "warn",
"noStaticOnlyClass": "error",
"noThisInStatic": "error",
"noUselessCatch": "error",
"noUselessConstructor": "error",
"noUselessFragments": "error",
"noUselessLabel": "error",
"noUselessRename": "error",
"noUselessSwitchCase": "error",
"noUselessTernary": "error",
"noUselessTypeConstraint": "error",
"noVoid": "error",
"useFlatMap": "error",
"useLiteralKeys": "error",
"useOptionalChain": "error",
"useSimpleNumberKeys": "error",
"useSimplifiedLogicExpression": "error"
},
"security": {
"noDangerouslySetInnerHtml": "error",
"noDangerouslySetInnerHtmlWithChildren": "error",
"noGlobalEval": "error"
},
"a11y": {
"noAccessKey": "error",
"noAriaHiddenOnFocusable": "error",
"noAriaUnsupportedElements": "error",
"noAutofocus": "error",
"noDistractingElements": "error",
"noHeaderScope": "error",
"noInteractiveElementToNoninteractiveRole": "error",
"noNoninteractiveElementToInteractiveRole": "error",
"noNoninteractiveTabindex": "error",
"noPositiveTabindex": "error",
"noRedundantAlt": "error",
"noRedundantRoles": "error",
"useFocusableInteractive": "error",
"useIframeTitle": "error",
"useKeyWithClickEvents": "error",
"useKeyWithMouseEvents": "error",
"useMediaCaption": "error",
"useSemanticElements": "error",
"useValidAnchor": "error",
"useValidAriaProps": "error",
"useValidAriaValues": "error",
"useValidLang": "error"
},
"performance": {
"noAccumulatingSpread": "warn",
"noDelete": "error"
}
},
"domains": {
"next": "all",
"react": "recommended"
}
},
"assist": {
"actions": {
"source": {
"organizeImports": {
"level": "off",
"options": {
"groups": [
":URL:",
":BLANK_LINE:",
[":BUN:", ":NODE:"],
":BLANK_LINE:",
":PACKAGE_WITH_PROTOCOL:",
":BLANK_LINE:",
[":PACKAGE:", "!@/**", "!#*", "!~*", "!$*", "!%*"],
":BLANK_LINE:",
["@/**", "#*", "~*", "$*", "%*"],
":BLANK_LINE:",
[":PATH:", "!./**", "!../**"],
":BLANK_LINE:",
["../**"],
":BLANK_LINE:",
["./**"]
],
"identifierOrder": "natural"
}
},
"useSortedAttributes": "on"
}
}
},
"javascript": {
"formatter": {
"arrowParentheses": "always",
"bracketSameLine": false,
"bracketSpacing": true,
"jsxQuoteStyle": "double",
"quoteProperties": "asNeeded",
"quoteStyle": "double",
"semicolons": "always",
"trailingCommas": "es5"
},
"globals": ["console", "process", "__dirname", "__filename"]
},
"json": {
"formatter": {
"trailingCommas": "none",
"indentStyle": "space",
"indentWidth": 2
}
},
"css": {
"formatter": {
"enabled": true,
"indentStyle": "space",
"indentWidth": 2,
"lineWidth": 100,
"quoteStyle": "double"
},
"linter": {
"enabled": true
}
},
"overrides": [
{
"includes": ["**/*.test.{js,ts,tsx}", "**/*.spec.{js,ts,tsx}", "**/__tests__/**"],
"linter": {
"rules": {
"suspicious": {
"noExplicitAny": "off",
"noConsole": "off"
},
"style": {
"noNonNullAssertion": "off"
}
}
}
},
{
"includes": ["**/next.config.{js,ts}", "**/tailwind.config.{js,ts}", "**/*.config.{js,ts}"],
"linter": {
"rules": {
"style": {
"noDefaultExport": "off"
},
"suspicious": {
"noExplicitAny": "off"
}
}
}
},
{
"includes": ["**/pages/**", "**/app/**/page.{tsx,jsx}", "**/app/**/layout.{tsx,jsx}"],
"linter": {
"rules": {
"style": {
"noDefaultExport": "off"
}
}
}
},
{
"includes": ["**/*.d.ts", "**/lib/env/*.ts"],
"linter": {
"rules": {
"style": {
"noNamespace": "off",
"useNamingConvention": {
"level": "error",
"options": {
"strictCase": false,
"conventions": [
{
"selector": {
"kind": "objectLiteralProperty"
},
"formats": ["CONSTANT_CASE", "camelCase"]
}
]
}
}
},
"suspicious": {
"noExplicitAny": "error"
},
"complexity": {
"noExcessiveCognitiveComplexity": {
"level": "error",
"options": {
"maxAllowedComplexity": 12
}
}
}
}
}
}
]
}
This configuration enables formatting, linting, assist actions, React rules, Next.js rules, accessibility checks, performance checks, and project-specific overrides.
Configure VS Code for Biome
Install the official Biome VS Code extension:
{
"recommendations": ["biomejs.biome"]
}
Then add this to .vscode/settings.json:
{
"editor.defaultFormatter": "biomejs.biome",
"editor.formatOnSave": true,
"biome.enabled": true,
"biome.requireConfiguration": true,
"editor.codeActionsOnSave": {
"source.fixAll.biome": "explicit",
"source.organizeImports.biome": "explicit"
}
}
This setup makes Biome your default formatter, enables formatting on save, applies safe fixes on save, and organizes imports when explicitly triggered.
Recommended VS Code Extensions
Create or update .vscode/extensions.json:
{
"recommendations": [
"biomejs.biome",
"bradlc.vscode-tailwindcss",
"ms-vscode.vscode-typescript-next",
"yzhang.markdown-all-in-one",
"formulahendry.auto-rename-tag",
"christian-kohler.path-intellisense"
]
}
These extensions pair well with Biome-powered JavaScript, TypeScript, React, and Next.js projects.
Add Useful Scripts
Add Biome scripts to your package.json:
{
"scripts": {
"format": "biome format --write .",
"lint": "biome lint .",
"lint:fix": "biome lint --write .",
"check": "biome check .",
"check:fix": "biome check --write .",
"ci": "biome ci ."
}
}
Use these commands during development:
npm run format
npm run lint
npm run check
For CI pipelines, use:
npm run ci
Format, Lint, and Check with Biome
Biome provides separate commands for formatting, linting, and full project checks.
Format Files
npx @biomejs/biome format --write .
Lint Files
npx @biomejs/biome lint .
Lint and Apply Safe Fixes
npx @biomejs/biome lint --write .
Format, Lint, and Organize Imports
npx @biomejs/biome check --write .
Run Biome in CI
npx @biomejs/biome ci .
The biome ci command is designed for continuous integration and helps enforce consistent code quality across your team.
Use Biome with Git Hooks
To catch issues before code is committed, you can run Biome through a Git hook tool such as Husky or Lefthook.
Example with a pre-commit command:
npx @biomejs/biome check --write .
For larger projects, run Biome only on staged files to keep commits fast.
Best Practices for Biome
Use these practices to get the best results from Biome:
- Keep
biome.jsonas the single source of truth. - Install Biome as a project dependency instead of relying only on a global install.
- Pin the Biome version for consistent formatting across the team.
- Use the official VS Code extension.
- Enable format on save.
- Run
biome ciin your deployment pipeline. - Use overrides for test files, generated files, config files, and framework-specific files.
- Keep ignores focused on generated output such as
dist,build,.next, andcoverage.
When to Use Overrides
Overrides are useful when some files need different linting behavior.
Common examples include:
- Test files that allow
anyorconsole - Next.js page and layout files that require default exports
- Config files that use CommonJS or framework-specific patterns
- Declaration files that use namespaces
- Generated files that should not be linted strictly
Using overrides keeps your main rules strict while allowing practical exceptions where needed.
Troubleshooting Biome in VS Code
If Biome does not format or lint correctly in VS Code, check the following:
Make Sure the Extension Is Installed
Install the official extension:
biomejs.biome
Confirm Your Project Has a Biome Config
Biome should find one of these configuration files:
biome.json
biome.jsonc
.biome.json
.biome.jsonc
Check the VS Code Setting Name
Use the latest setting name:
{
"biome.requireConfiguration": true
}
Avoid older or incorrect setting names such as biome.requireConfig.
Restart the Biome Language Server
Open the VS Code command palette and restart the Biome extension or reload the VS Code window.
Run Biome from the Terminal
If VS Code behaves unexpectedly, confirm Biome works from the CLI:
npx @biomejs/biome check .
If the CLI works but VS Code does not, the issue is likely related to editor settings or workspace configuration.
Wrap-up
One biome.json, the official VS Code extension, formatOnSave, and biome ci in the pipeline. That’s it — no more config drift between ESLint, Prettier, and their config-prettier middleware.
If you’re starting a new TypeScript or Next.js repo, Biome is the default I’d reach for now. On an existing repo with deep ESLint custom rules, migration takes a weekend — worth it on most projects, but worth pricing first.
Related
- Type-safe environment configuration for Next.js — pairs well with Biome for a clean Next.js base.
- Type-safe environment validation in Laravel & PHP — same “validate at boot” mindset on the backend side.