Biome V2: Taming Your Imports for Perfect Order
Keeping your import statements organized is crucial for code readability and maintainability. When working on larger projects, a consistent import order can make a world of difference. Biome V2 is a powerful formatter that helps achieve this, but sometimes configuring it to your exact preferences can be a bit tricky.
Recently, a user, Cac0, faced a common challenge with Biome V2's organizeImports
feature: getting npm package imports to appear at the top, followed by project-specific imports, and then other general imports. Let's dive into the problem, the debugging process, and the solution that brought order to their imports. Conaclos, a core contributor to Biome, also provided valuable insights during this discussion.
The Problem: Imports Out of Order
Cac0's goal was clear:
- npm packages (like
react
andreact-scan
) at the very top. - A blank line.
- Project-specific imports (e.g., components from
@components
, utilities from@utils
). - Another blank line.
- Any other imports not covered by the above.
However, despite various attempts with Biome's groups
configuration, their npm package imports were consistently appearing at the bottom, not the top. They were using pnpm, React 19, and Biome v2.
Initial Troubleshooting and Misconceptions
Initial discussions revolved around Biome's predefined groups. It was clarified that :npm:
isn't a valid group, but :PACKAGE:
is designed to match package imports. Cac0 tried using :PACKAGE:
, but the issue persisted.
One key insight came from Daryl, suggesting that the pattern @lib/*
might be incorrect and should instead be @lib/**
for matching multiple levels within the directory. Cac0 then revised their groups
configuration to:
"groups": [
":NODE:",
"./node_modules/**",
":PACKAGE:",
":BLANK_LINE:",
["app", "@components/**", "@lib/**", "@utils/**"],
":BLANK_LINE:",
["./**"]
]
Still, the imports weren't behaving as expected. There was a brief thought that pnpm
might be the culprit, but this was ruled out as import organization is a formatter concern, not a package manager one. Emanuele also pointed out that directly matching node_modules
in the groups
isn't standard practice for import organization.
The Breakthrough: Aliases and tsconfig.app.json
The turning point came when Cac0 realized the issue wasn't solely with Biome's configuration but also with how their TypeScript aliases were defined in tsconfig.app.json
.
Initially, their paths
in tsconfig.app.json
looked like this:
// ...
"paths": {
"@assets/*": ["assets/*"],
"@common/*": ["components/common/*"],
"@lib/*": ["components/lib/*"],
"@api/*": ["api/*"],
"@utils/*": ["utils/*"]
}
// ...
The crucial fix was to add a forward slash (/
) after the @
in the aliases:
"paths": {
"@/assets/*": ["assets/*"],
"@/common/*": ["components/common/*"],
"@/lib/*": ["components/lib/*"],
"@/api/*": ["api/*"],
"@/utils/*": ["utils/*"]
}
This seemingly minor change had a significant impact. By using "@/lib/*"
instead of "@lib/*"
, Cac0 theorized (and correctly so) that this new pattern for project-specific imports no longer conflicted with how Biome identifies standard npm package imports (which typically don't have @/
in their names unless they are scoped packages like @scope/package
).
The Final, Working Configuration
With the updated tsconfig.app.json
aliases, Cac0's Biome groups
configuration finally achieved the desired import order:
// biome.json
{
// ...
"organizeImports": {
"enabled": true,
"ignore": [],
"groups": [
":PACKAGE:",
":BLANK_LINE:",
["@/assets/**"],
":BLANK_LINE:",
["@/common/**", "@/lib/**", "@/api/**", "@/utils/**"],
":BLANK_LINE:",
["./**"]
]
}
// ...
}
And here's an example of how a component's imports would now be formatted correctly:
// Example Component
import { scan } from "react-scan"; // :PACKAGE:
import Test from "@/lib/test"; // Project-specific (matched by "@/lib/**")
console.log(scan);
const TextComponent: React.FC = () => {
return (
<div>
Text
<Test />
</div>
);
};
export default TextComponent;
Understanding Group Matching and Shadowing
Conaclos further explained an important concept: groups are matched in order, and one group matcher can "shadow" succeeding groups. This means if a broad pattern like :PACKAGE:
matches all @
prefixed imports, then subsequent groups attempting to match specific @lib
or @components
patterns might be rendered useless.
Cac0's final solution worked because the "@/..."
pattern for aliases ensures that these project-specific imports are not matched by the generic :PACKAGE:
group, allowing them to be caught by their dedicated groups later in the groups
array.
Note that all other imports are placed at the end by Biome's organizer, thus you can omit the `"./"` part as it represents the fallback for any unmatched imports.**
Conaclos also suggested using the predefined group :ALIAS:
which matches patterns like @/**
, #*/**
, and ~/**
, offering a more concise way to categorize alias imports:
"groups": [
":PACKAGE_WITH_PROTOCOL:",
":PACKAGE:",
":BLANK_LINE:",
"@/assets/**", // Or include in :ALIAS: if applicable
":BLANK_LINE:",
":ALIAS:",
":BLANK_LINE:"
]
Key Takeaways
-
Biome's
organizeImports
is powerful: It offers granular control over your import order. -
Understand Predefined Groups: Familiarize yourself with groups like
:PACKAGE:
,:NODE:
, and:ALIAS:
. -
Order Matters: The order of your
groups
array is crucial, as earlier groups can shadow later ones. -
Alias Configuration is Key: Ensure your TypeScript or JavaScript aliases (e.g., in
tsconfig.json
) are defined in a way that allows Biome to distinguish between npm packages and your project-specific modules. Using a unique prefix like~/
or@/
for project aliases can simplify Biome's matching. - Debug Incrementally: When facing issues, start with a minimal, working configuration (perhaps from documentation) and then add your customizations step by step to identify the breaking change.
-
Redundant Fallbacks: Biome automatically places unmatched imports at the end, so explicit catch-all patterns like
"./**"
in your last group might be unnecessary.
Refer official docs: https://biomejs.dev/assist/actions/organize-imports/
Have you faced similar challenges with Biome or other formatters? Share your tips and tricks in the comments below!
Top comments (0)