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.

Soman Bandesha Updated 8 min read
Configure Biome for JavaScript & TypeScript in VS Code

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 ci mode 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.

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.json as 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 ci in 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, and coverage.

When to Use Overrides

Overrides are useful when some files need different linting behavior.

Common examples include:

  • Test files that allow any or console
  • 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.