These integration guides are not official documentation and the Strapi Support Team will not provide assistance with them.
What Is Biome?
Biome is a Rust-based toolchain that consolidates linting, formatting, and import sorting for JavaScript and TypeScript into a single unified tool. It positions itself as a direct replacement for the ESLint plus Prettier combination that most Node.js projects currently use.
The tool includes 436 linting rules sourced from ESLint, TypeScript ESLint, and other ecosystem tools. Its formatter maintains 97% compatibility with Prettier while running significantly faster due to its compiled Rust architecture and parallel processing capabilities.
Sign up for the Logbook, Strapi's Monthly newsletter
Why Integrate Biome with Strapi
Strapi v5 introduced a TypeScript-first architecture that pairs naturally with Biome's capabilities. Here's what the integration offers for your Strapi development workflow:
- Faster feedback loops: Biome's Rust-based architecture with parallel processing and incremental caching provides performance advantages during development when working across Strapi's admin panel customizations, API routes, and custom plugins simultaneously.
- Single configuration file: Consolidate
.eslintrc,.prettierrc, and import sorting configurations into onebiome.json, reducing maintenance burden and eliminating conflicts between tools. - TypeScript-aware linting: Biome's type inference engine enables type-aware linting rules, including detection of floating promises in async contexts, without requiring the TypeScript compiler in your linting pipeline.
- Unified tooling approach: Biome handles formatting, linting, and import organization through a single tool with built-in configuration, reducing dependency management complexity compared to managing separate
eslint-config-*andprettier-plugin-*packages. - CI/CD optimization: The
biome cicommand replaces multiple tool invocations in your pipeline, reducing build times and simplifying workflow configuration. - Consistent team standards: One configuration file in version control ensures everyone runs identical checks, whether in their IDE, pre-commit hooks, or CI environment.
How to Integrate Biome with Strapi
Prerequisites
Before starting, ensure you have:
- Node.js 18 or later installed
- A Strapi v5 project (create one with
npx create-strapi@latest my-projectif needed) - Package manager: npm, pnpm, or yarn
- Git initialized in your project (for hooks integration)
Familiarity with Strapi's project structure helps when configuring file exclusions.
Step 1: Install Biome
Add Biome as a development dependency with exact version pinning using the -E flag:
# npm
npm install -D -E @biomejs/biome
# pnpm
pnpm add -D -E @biomejs/biome
# yarn
yarn add -D -E @biomejs/biome
# bun
bun add -D -E @biomejs/biomeThe -E flag (or --save-exact) ensures an exact version is pinned in package.json, preventing unexpected updates that could introduce breaking changes in your Strapi v5 project.
Step 2: Initialize Configuration
Generate the configuration file using the biome init command:
npx @biomejs/biome initThis creates a biome.json or biome.jsonc file at your project root with sensible defaults. The generated file enables formatting, linting with recommended rules, and import organization.
Step 3: Configure for Strapi v5
Strapi v5 generates several directories that shouldn't be linted: build artifacts like dist/ and build/, temporary files in .tmp/, auto-generated admin files in .strapi/, and auto-generated TypeScript type definitions in src/types/. Replace the default biome.json with this Strapi-optimized configuration:
{
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
"vcs": {
"enabled": true,
"clientKind": "git",
"useIgnoreFile": true
},
"organizeImports": {
"enabled": true
},
"linter": {
"enabled": true,
"rules": {
"recommended": true,
"complexity": {
"noExcessiveCognitiveComplexity": "error",
"noUselessCatch": "error"
},
"correctness": {
"noUnusedVariables": "error",
"noUndeclaredVariables": "error",
"noDebugger": "error"
},
"suspicious": {
"noFloatingPromises": "error",
"noDoubleEquals": "error",
"noImplicitCoercions": "error",
"noConsoleLog": "warn"
},
"style": {
"useConst": "error",
"useExplicitFunctionReturnType": "warn"
}
}
},
"formatter": {
"enabled": true,
"formatWithErrors": false,
"indentStyle": "space",
"indentWidth": 2,
"lineWidth": 100,
"lineEnding": "lf"
},
"javascript": {
"formatter": {
"quoteStyle": "single",
"jsxQuoteStyle": "double",
"semicolons": "always",
"trailingCommas": "es5",
"arrowParentheses": "always"
}
},
"files": {
"include": ["src/**/*", "config/**/*"],
"ignore": [
"node_modules",
"dist",
"build",
".tmp",
".strapi",
".cache",
"src/types",
"public/uploads",
"coverage",
"*.min.js"
]
}
}Configuration breakdown:
- VCS integration: Respects your
.gitignorepatterns automatically noFloatingPromises: Critical for Strapi's async controllers and services, catches unhandled promises that could cause silent failures in asynchronous operations- File inclusions: Targets
src/(your custom API logic, plugins, and admin customizations) andconfig/(Strapi configuration files) src/typesexclusion: Strapi v5 automatically generates TypeScript definitions in this directory. Excluding it from linting prevents noise since these files are auto-generated and not manually edited by developers
Step 4: Add npm Scripts
Add convenience scripts to package.json:
{
"scripts": {
"lint": "biome check .",
"lint:fix": "biome check --write .",
"format": "biome format .",
"format:fix": "biome format --write .",
"biome:check": "biome check .",
"biome:ci": "biome ci ."
}
}Each script serves a different purpose:
| Script | Behavior |
|---|---|
lint | Reports linting issues in code without modifying files, using the biome check command |
lint:fix | Applies safe, automatically fixable linting errors using biome check --write, transforming code to match linting rules |
format | Shows code formatting differences according to Biome's configuration without writing changes to disk |
format:fix | Formats all code according to Biome configuration and writes changes directly to files using biome format --write |
biome:ci | CI-optimized unified check that runs linting, formatting, and import organization simultaneously, designed to fail on any issue encountered in continuous integration pipelines |
Step 5: Verify the Setup
Run the linter against your project using the biome check command:
npm run lintIf everything is configured correctly, Biome processes your src/ and config/ directories while ignoring Strapi's auto-generated files and build artifacts. Fix any reported issues:
npm run lint:fixStep 6: Configure VS Code Integration
Install the Biome VS Code extension from the marketplace, then configure workspace settings in .vscode/settings.json:
{
"editor.defaultFormatter": "biomejs.biome",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.biome": "explicit",
"source.organizeImports.biome": "explicit"
},
"[javascript]": {
"editor.defaultFormatter": "biomejs.biome"
},
"[typescript]": {
"editor.defaultFormatter": "biomejs.biome"
},
"[json]": {
"editor.defaultFormatter": "biomejs.biome"
}
}Now saving a file triggers formatting, import organization, and safe lint fixes automatically.
Step 7: Set Up Git Hooks
Pre-commit hooks catch issues before they reach your repository. Install dependencies:
npm install -D husky lint-stagedInitialize Husky:
npx husky initAdd the pre-commit hook:
echo "npx lint-staged" > .husky/pre-commitConfigure lint-staged in package.json:
{
"lint-staged": {
"*.{js,ts,jsx,tsx,json}": ["biome check --write --no-errors-on-unmatched"]
}
}The --no-errors-on-unmatched flag prevents Biome from throwing errors when it encounters files that don't match its configured linting patterns, which is particularly useful when using lint-staged with glob patterns that may include files outside Biome's scope.
Step 8: Add CI/CD Integration
For GitHub Actions, create .github/workflows/code-quality.yml:
name: Code Quality
on: [push, pull_request]
jobs:
biome:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- run: npm ci
- run: npx @biomejs/biome ci .The biome ci command is optimized for CI environments. It performs checks in read-only mode without writing files to disk and exits with non-zero status codes on issues for pipeline integration.
Apply Fixes
Before committing code to your Strapi v5 project, Biome can automatically apply fixes to resolve linting issues and formatting inconsistencies.
The --write flag applies only safe, automatically fixable issues:
npx @biomejs/biome check --write .Safe fixes include formatting inconsistencies, import organization, and unambiguous rule violations like useConst converting var to const.
For potentially unsafe fixes that may require review, add the --unsafe flag:
npx @biomejs/biome check --write --unsafe .Unsafe fixes should only be applied after careful review, as they may change code behavior.
Project Example: Blog API with Enforced Code Quality
Let's build a practical example: a blog API with content types defined in Strapi v5's project structure and custom business logic, demonstrating how Biome catches common issues during development.
Create the Strapi Project
npx create-strapi@latest blog-api --quickstart
cd blog-apiInitialize Biome
npm install -D -E @biomejs/biome
npx @biomejs/biome initReplace biome.json with the Strapi-optimized configuration from Step 3 above.
Create a Content Type
Using the Strapi Admin Panel at http://localhost:1337/admin, create an "Article" collection type with these fields:
title(Text, required)content(Rich Text)slug(UID, attached to title)publishedAt(DateTime)
Add Custom Service Logic
Create a custom service with intentional issues that Biome will catch. Add this to src/api/article/services/article.ts:
import { factories } from '@strapi/strapi';
export default factories.createCoreService('api::article.article', ({ strapi }) => ({
async findPublished() {
let articles = strapi.documents('api::article.article').findMany({
filters: {
publishedAt: {
$notNull: true,
},
},
sort: { publishedAt: 'desc' },
});
return articles;
},
async findBySlug(slug) {
const article = await strapi.documents('api::article.article').findFirst({
filters: { slug },
});
if (slug == null) {
return null;
}
return article;
},
}));Run the Linter
npm run lintBiome reports code issues through its comprehensive rule set:
src/api/article/services/article.ts:6:5 lint/style/useConst
Use 'const' instead of 'let'
src/api/article/services/article.ts:6:21 lint/suspicious/noFloatingPromises
Unhandled promise - must be awaited or returned
src/api/article/services/article.ts:24:9 lint/suspicious/noDoubleEquals
Use === instead of ==The noFloatingPromises error is critical. In Strapi v5 services, this pattern is incorrect:
// ❌ Incorrect - floating promise
async findPublished() {
let articles = strapi.documents('api::article.article').findMany();
return articles;
}The corrected pattern properly awaits the promise:
// ✅ Correct - promise awaited
async findPublished() {
const articles = await strapi.documents('api::article.article').findMany();
return articles;
}Apply Fixes
npm run lint:fixBiome automatically fixes safe issues. However, noFloatingPromises requires manual intervention since you need to decide whether to await or return the promise.
The corrected service:
import { factories } from '@strapi/strapi';
export default factories.createCoreService('api::article.article', ({ strapi }) => ({
async findPublished() {
const articles = await strapi.documents('api::article.article').findMany({
filters: {
publishedAt: {
$notNull: true,
},
},
sort: { publishedAt: 'desc' },
});
return articles;
},
async findBySlug(slug: string) {
if (slug === null) {
return null;
}
const article = await strapi.documents('api::article.article').findFirst({
filters: { slug },
});
return article;
},
}));Create a Custom Controller
Add a controller that uses the custom service at src/api/article/controllers/article.ts:
import { factories } from '@strapi/strapi';
export default factories.createCoreController('api::article.article', ({ strapi }) => ({
async findPublished(ctx) {
const articles = await strapi.service('api::article.article').findPublished();
ctx.body = {
data: articles,
meta: {
count: articles.length,
},
};
},
async findBySlug(ctx) {
const { slug } = ctx.params;
const article = await strapi.service('api::article.article').findBySlug(slug);
if (!article) {
return ctx.notFound('Article not found');
}
ctx.body = { data: article };
},
}));Add Custom Routes
Create src/api/article/routes/custom-routes.ts:
export default {
routes: [
{
method: 'GET',
path: '/articles/published',
handler: 'article.findPublished',
config: {
auth: false,
},
},
{
method: 'GET',
path: '/articles/slug/:slug',
handler: 'article.findBySlug',
config: {
auth: false,
},
},
],
};Final Verification
Run the full check:
npx @biomejs/biome check .With properly structured code, Biome reports no issues. Your Strapi API now has enforced code quality standards that run on every save, every commit, and every CI build.
Strapi Open Office Hours
If you have any questions about Strapi 5 or just would like to stop by and say hi, you can join us at Strapi's Discord Open Office Hours, Monday through Friday, from 12:30 pm to 1:30 pm CST: Strapi Discord Open Office Hours.
For more details, visit the Strapi documentation and Biome documentation.
Get Started in Minutes
npx create-strapi-app@latest in your terminal and follow our Quick Start Guide to build your first Strapi project.