Skip to content

organizeImports

.vscode/settings.json
{
"editor.codeActionsOnSave": {
"source.organizeImports.biome": "explicit",
"source.fixAll.biome": "explicit"
}
}
biome.json
{
"assist": {
"actions": {
"source": {
"organizeImports": "on"
}
}
}
}

Sorts imports and exports in your JavaScript and TypeScript files.

By default, imports and exports are sorted by “distance” from the current file:

  1. URLs such as https://example.org.
  2. Packages with a protocol such as node:path, bun:test, jsr:@my?lib, or npm:lib.
  3. Packages such as mylib or @my/lib.
  4. Aliases: sources starting with @/, #, ~, $, or %. They usually are Node.js subpath imports or TypeScript path aliases.
  5. Absolute and relative paths.

Imports and exports with the same distance are sorted using a natural sort order such that A < a < a9 < a10 < B < b

The action also merges imports and exports from the same source, sorts named specifiers and attributes using a natural sort order.

For example, the following code…

import sibling from "./file.js";
import internal from "#alias";
import fs from "fs";
import { test } from "node:test";
import path from "node:path";
import { mock } from "node:test";
import parent from "../parent.js";
import scopedLibUsingJsr from "jsr:@scoped/lib";
import data from "https://example.org";
import { B, A, b, a10, a9 } from "lib";
import scopedLib from "@scoped/lib";
export { Y } from "dep";
export * from "./inner.js";
export { X } from "dep";

…is sorted as follows:

import data from "https://example.org";
import scopedLibUsingJsr from "jsr:@scoped/lib";
import path from "node:path";
import { mock, test } from "node:test";
import scopedLib from "@scoped/lib";
import fs from "fs";
import { A, a9, a10, B, b } from "lib";
import internal from "#alias";
import parent from "../parent.js";
import sibling from "./file.js";
export { X, Y } from "dep";
export * from "./inner.js";

The action provides several options to customize how imports and exports are ordered:

  • groups allows to group imports and exports before sorting them; It allows expressing custom order between imports or exports.
  • identifierOrder allows changing how named specifiers and attributes are sorted

You can customize how imports and exports are grouped using the groups option. The option accepts an array of group matchers, which in their simplest form are glob patterns or predefined group matchers. Imports and exports that don’t match any group are automatically moved after all the groups.

Groups are always matched in order, so earlier matchers take priority. To exclude some imports of a group, you can use an array of group matchers with negated matchers, prefixed with !. In the following example, we use the negated glob matcher !@myown/**, to exclude @myown/package from the :PACKAGE: group.

With this configuration…

biome.json
{
"assist": {
"actions": {
"source": {
"organizeImports": {
"options": {
"groups": [
[
":BUN:",
":NODE:"
],
":BLANK_LINE:",
[
":PACKAGE:",
"!@myown/**"
],
":BLANK_LINE:",
"@myown/**",
":BLANK_LINE:",
[
":ALIAS:",
":PATH:"
]
]
}
}
}
}
}
}

…the following code…

import aliased from "@/components/Button";
import lib from "lib";
import path from "node:path";
import sibling from "./file.js";
import myown from "@myown/package";
import scopedLib from "@scoped/lib";
import fs from "fs";

…is sorted as:

import fs from "fs";
import path from "node:path";
import scopedLib from "@scoped/lib";
import lib from "lib";
import myown from "@myown/package";
import aliased from "@/components/Button";
import sibling from "./file.js";

Each entry in the groups array is a group matcher that can be:

  • A predefined group like :NODE:, :BUN:, or :PACKAGE:
  • A glob pattern like @my/lib/**; the action supports a limited set of globs.
  • Type-only imports like { "type": true }
  • A combination of the above, e.g. [":BUN:", ":NODE:"]
  • :BLANK_LINE: to insert a blank line between groups
  • :URL:: sources starting with https:// or http://
  • :NODE:: Node.js built-in modules (node:path, fs, path, etc.)
  • :BUN:: Bun built-in modules (bun:test, bun, etc.)
  • :PACKAGE_WITH_PROTOCOL:: packages with a protocol (jsr:@my/lib, npm:lib)
  • :PACKAGE:: bare and scoped packages (lib, @scoped/lib)
  • :ALIAS:: path aliases starting with #, @/, ~, $, or %
  • :PATH:: absolute and relative paths

Use a type-only matcher to separate import type from regular imports: Setting "type": true matches only import type and export type statements. Setting "type": false matches only non-type imports and exports.

Given the following configuration…

biome.json
{
"assist": {
"actions": {
"source": {
"organizeImports": {
"options": {
"groups": [
{
"type": false,
"source": [
"@my/lib",
"@my/lib/**"
]
},
[
"@my/lib",
"@my/lib/**"
]
]
}
}
}
}
}
}

…the following code…

import type { T } from "@my/lib";
import { V } from "@my/lib";

…is sorted as:

import { V } from "@my/lib";
import type { T } from "@my/lib";

By default, attributes, imported and exported names are sorted with a natural sort order. Opt for a lexicographic sort, also referred as binary sort, by setting the identifierOrder option to lexicographic:

biome.json
{
"assist": {
"actions": {
"source": {
"organizeImports": {
"options": {
"identifierOrder": "lexicographic"
}
}
}
}
}
}
import { var1, var2, var21, var11, var12, var22 } from "my-package" with { "att10": "", "att2": "" };
export { var1, var2, var21, var11, var12, var22 };
code-block.js:1:1 assist/source/organizeImports  FIXABLE  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

The imports and exports are not sorted.

> 1 │ import { var1, var2, var21, var11, var12, var22 } from “my-package” with { “att10”: "", “att2”: "" };
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
2 │
3 │ export { var1, var2, var21, var11, var12, var22 };

Safe fix: Organize Imports (Biome)

1 - import·{·var1,·var2,·var21,·var11,·var12,·var22·}·from·my-package·with·{·att10:·"",·att2:·""·};
1+ import·{·var1,·var11,·var12,·var2,·var21,·var22·}·from·my-package·with·{·att10:·"",·att2:·""·};
2 2
3 - export·{·var1,·var2,·var21,·var11,·var12,·var22·};
3+ export·{·var1,·var11,·var12,·var2,·var21,·var22·};
4 4

Note that this order doesn’t change how import and export sources are sorted.

The following example moves the Node.js and bun built-ins at the top of the file, and adds a blank line just after them. Other imports are placed after this blank line.

biome.json
{
"assist": {
"actions": {
"source": {
"organizeImports": {
"options": {
"groups": [
[
":BUN:",
":NODE:"
],
":BLANK_LINE:"
]
}
}
}
}
}
}
import { test } from "bun:test";
import path from "node:path";
import { A } from "@my/package";
import { $ } from "bun";
import fs from "fs";
code-block.ts:1:1 assist/source/organizeImports  FIXABLE  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

The imports and exports are not sorted.

> 1 │ import { test } from “bun:test”;
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
2 │ import path from “node:path”;
3 │ import { A } from “@my/package”;

Safe fix: Organize Imports (Biome)

1 1 import { test } from “bun:test”;
2 2 import path from “node:path”;
3 - import·{·A·}·from·@my/package;
4 - import·{·$·}·from·bun;
5 - import·fs·from·fs;
3+ import·{·$·}·from·bun;
4+ import·fs·from·fs;
5+
6+ import·{·A·}·from·@my/package;
6 7

Let’s assume that all your monorepo packages are scoped by @mycompany. The following example groups all monorepo imports after imports of external dependencies.

Because groups are matched in order, the first group has to exclude monorepo imports. Indeed, :PACKAGE: matches imports like @mycompany/db, and thus must be excluded thanks to the exception !@mycompany/**.

biome.json
{
"assist": {
"actions": {
"source": {
"organizeImports": {
"options": {
"groups": [
[
":PACKAGE:",
":PACKAGE_WITH_PROTOCOL:",
"!@mycompany/**"
],
":BLANK_LINE:",
[
"@mycompany/**"
],
":BLANK_LINE:"
]
}
}
}
}
}
}
import { Button } from "@mycompany/ui";
import express from "express";
import { db } from "@mycompany/db";
import { handler } from "./handler.js";
import { A } from "./file.js"
code-block.js:1:1 assist/source/organizeImports  FIXABLE  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

The imports and exports are not sorted.

> 1 │ import { Button } from “@mycompany/ui”;
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
2 │ import express from “express”;
3 │ import { db } from “@mycompany/db”;

Safe fix: Organize Imports (Biome)

1 - import·{·Button·}·from·@mycompany/ui;
2 - import·express·from·express;
3 - import·{·db·}·from·@mycompany/db;
4 - import·{·handler·}·from·./handler.js;
5 - import·{·A·}·from·./file.js
1+ import·express·from·express;
2+
3+ import·{·db·}·from·@mycompany/db;
4+ import·{·Button·}·from·@mycompany/ui;
5+
6+ import·{·A·}·from·./file.js
7+ import·{·handler·}·from·./handler.js;
6 8

In the following example, react and libraries like react-dom are grouped together. A blank line separates them from the other imports placed directly below.

biome.json
{
"assist": {
"actions": {
"source": {
"organizeImports": {
"options": {
"groups": [
[
"react",
"react/**",
"react-*",
"react-*/**"
],
":BLANK_LINE:"
]
}
}
}
}
}
}
import lib from "lib";
import { useState } from "react";
import { render } from "react-dom/client";
code-block.js:1:1 assist/source/organizeImports  FIXABLE  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

The imports and exports are not sorted.

> 1 │ import lib from “lib”;
^^^^^^^^^^^^^^^^^^^^^^
2 │ import { useState } from “react”;
3 │ import { render } from “react-dom/client”;

Safe fix: Organize Imports (Biome)

1 - import·lib·from·lib;
2 - import·{·useState·}·from·react;
3 - import·{·render·}·from·react-dom/client;
1+ import·{·useState·}·from·react;
2+ import·{·render·}·from·react-dom/client;
3+
4+ import·lib·from·lib;
4 5

The following example groups style imports together and place them after other imports. Because groups are matched in order, the first group has to exclude style imports. ** matches everything and is followed by the two exceptions that exclude style imports.

biome.json
{
"assist": {
"actions": {
"source": {
"organizeImports": {
"options": {
"groups": [
[
"**",
"!**/*.css",
"!**/*.scss"
],
":BLANK_LINE:",
[
"**/*.css",
"**/*.scss"
]
]
}
}
}
}
}
}
import "./styles/reset.css";
import { useState } from "react";
import styles from "./Component.module.css";
import { Button } from "@/components/Button";
code-block.js:1:1 assist/source/organizeImports  FIXABLE  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

The imports and exports are not sorted.

> 1 │ import ”./styles/reset.css”;
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
2 │ import { useState } from “react”;
3 │ import styles from ”./Component.module.css”;

Safe fix: Organize Imports (Biome)

1 1 import ”./styles/reset.css”;
2 2 import { useState } from “react”;
3 - import·styles·from·./Component.module.css;
4 - import·{·Button·}·from·@/components/Button;
3+ import·{·Button·}·from·@/components/Button;
4+
5+ import·styles·from·./Component.module.css;
5 6

The following example places test-related utilities at the top of the file. They are separated from other imports by a blank line.

biome.json
{
"assist": {
"actions": {
"source": {
"organizeImports": {
"options": {
"groups": [
[
"vitest",
"vitest/**",
"@testing-library",
"@testing-library/**",
"jest",
"@jest/**"
],
":BLANK_LINE:"
]
}
}
}
}
}
}
import { render } from "@testing-library/react";
import { Button } from "@/components/Button";
import { describe, it, expect } from "vitest";
import { server } from "./mocks/server";
code-block.js:1:1 assist/source/organizeImports  FIXABLE  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

The imports and exports are not sorted.

> 1 │ import { render } from “@testing-library/react”;
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
2 │ import { Button } from ”@/components/Button”;
3 │ import { describe, it, expect } from “vitest”;

Safe fix: Organize Imports (Biome)

1 1 import { render } from “@testing-library/react”;
2 - import·{·Button·}·from·@/components/Button;
3 - import·{·describe,·it,·expect·}·from·vitest;
2+ import·{·describe,·expect,·it·}·from·vitest;
3+
4+ import·{·Button·}·from·@/components/Button;
4 5 import { server } from ”./mocks/server”;
5 6

Use the following configuration to place import type and export type at the top of the file:

biome.json
{
"assist": {
"actions": {
"source": {
"organizeImports": {
"options": {
"groups": [
{
"type": true
}
]
}
}
}
}
}
}
import { V } from "my-package";
import type { T } from "my-package";
code-block.ts:1:1 assist/source/organizeImports  FIXABLE  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

The imports and exports are not sorted.

> 1 │ import { V } from “my-package”;
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
2 │ import type { T } from “my-package”;
3 │

Safe fix: Organize Imports (Biome)

1 - import·{·V·}·from·my-package;
2 - import·type·{·T·}·from·my-package;
1+ import·type·{·T·}·from·my-package;
2+ import·{·V·}·from·my-package;
3 3

To place them after other imports, use the following configuration.

biome.json
{
"assist": {
"actions": {
"source": {
"organizeImports": {
"options": {
"groups": [
{
"type": false
}
]
}
}
}
}
}
}

The following code…

import type { T } from "my-package";
import { V } from "my-package";

…is organized as:

import { V } from "my-package";
import type { T } from "my-package";

Note that you may want to use the lint rule useImportType and its style to enforce the use of import type instead of import { type }.

With the following configuration…

biome.json
{
"linter": {
"rules": {
"style": {
"useImportType": {
"level": "on",
"options": {
"style": "separatedType"
}
}
}
}
},
"assist": {
"enabled": true,
"actions": {
"source": {
"organizeImports": "on"
}
}
}
}

…the following code…

import type { T1 } from "a";
import { type T2, V } from "a";

…is organized as:

import type { T1, T2 } from "a";
import { V } from "a";

Maximize import merging with useImportType

Section titled “Maximize import merging with useImportType”

To merge type-only imports (import type { T }) with regular imports (import { V }), enable useImportType with inlineType:

biome.json
{
"linter": {
"rules": {
"style": {
"useImportType": {
"level": "on",
"options": {
"style": "inlineType"
}
}
}
}
},
"assist": {
"enabled": true,
"actions": {
"source": {
"organizeImports": "on"
}
}
}
}

The following code…

import type { T } from "a";
import { V } from "a";

…is organized as:

import { type T, V } from "a";

This section provides an in-depth explanation of the internal mechanics of the action.

First, let’s agree on the terminology that we will use in this section.

import A from "@my/lib" with { "attribute1": "value" };
^^^^^^^^ ^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^
kind source attributes
export * from "@my/lib" with { "attribute1": "value" };
^^^^^^^^ ^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^
kind source attributes

Before sorting, imports and exports are divided into chunks. A chunk is a sequence of adjacent imports or exports. The action never moves imports or exports across chunk boundaries.

Chunks are separated by:

  • Switching between imports and exports
  • Any statement that is not an import or an export
  • Bare imports also called side-effect imports (import "polyfill"); Each forms its own chunk.
  • A comment followed by a blank line that we call a detached comment; See the comment handling section for more details.

The following example shows how imports and exports are chunked.

// chunk 1
import A from "a";
import * as B from "b";
// chunk 2 (a bare import creates its own chunk)
import "x";
// Chunk 3
import A from "a";
// Still same chunk (blank line alone doesn't split)
import * as B from "b";
// Detached comment (followed by blank line)
// New chunk starts here
import { C } from "c";

The action enforces the presence of a blank line between different chunks. Bare imports adjacent to a chunk of imports are not separated by a blank line.

As described in the preliminary section, Imports and exports of a chunk are sorted by “distance” from the current file.

When two imports share the same source, they are ordered by kind:

  1. Namespace type import / Namespace type export
  2. Default type import
  3. Named type import / Named type export
  4. Namespace import / Namespace export
  5. Combined default and namespace import
  6. Default import
  7. Combined default and named import
  8. Named import / Named export

Imports and exports with attributes (with { ... }) are always placed first. For example, the following code…

import * as namespaceImport from "same-source";
import type * as namespaceTypeImport from "same-source";
import type { namedTypeImport } from "same-source";
import defaultNamespaceCombined, * as namespaceCombined from "same-source";
import defaultNamedCombined, { namedCombined } from "same-source";
import defaultImport from "same-source";
import type defaultTypeImport from "same-source";
import { importWithAttribute } from "same-source" with { "attribute": "value" } ;

is sorted as follows:

import { importWithAttribute } from "same-source" with { "attribute": "value" } ;
import type * as namespaceTypeImport from "same-source";
import type defaultTypeImport from "same-source";
import type { namedTypeImport } from "same-source";
import * as namespaceImport from "same-source";
import defaultNamespaceCombined, * as namespaceCombined from "same-source";
import defaultImport from "same-source";
import defaultNamedCombined, { namedCombined } from "same-source";

This kind order cannot be changed.

Named imports, named exports, and import attributes are also sorted, as shown in the following example.

import { a, b, A, B, c10, c9 } from "a";
export { a, b, A, B, c10, c9 } from "a";
import special from "special" with { "type": "ty", "metadata": "data" };
code-block.js:1:1 assist/source/organizeImports  FIXABLE  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

The imports and exports are not sorted.

> 1 │ import { a, b, A, B, c10, c9 } from “a”;
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
2 │
3 │ export { a, b, A, B, c10, c9 } from “a”;

Safe fix: Organize Imports (Biome)

1 - import·{·a,·b,·A,·B,·c10,·c9·}·from·a;
1+ import·{·A,·a,·B,·b,·c9,·c10·}·from·a;
2 2
3 - export·{·a,·b,·A,·B,·c10,·c9·}·from·a;
3+ export·{·A,·a,·B,·b,·c9,·c10·}·from·a;
4 4
5 - import·special·from··special·with·{·type:·ty,·metadata:·data·};
5+ import·special·from··special·with·{·metadata:·data,·type:·ty·};
6 6

Imports from the same source in the same chunk are merged when possible.

The following code…

import type { T1 } from "package";
import type { T2 } from "package";
import * as ns from "package";
import D1 from "package";
import D2 from "package";
import { A } from "package";
import { B } from "package";
import { type T3 } from "package";

…becomes:

import type { T1, T2 } from "package";
import D1, * as ns from "package";
import D2, { A, B, type T3 } from "package";

With useImportType set to separatedType, the result is:

import type { T1, T2, T3 } from "package";
import D1, * as ns from "package";
import D2, { A, B } from "package";

Comments directly above an import (attached comments) move with that import when it is sorted. Comments followed by a blank line (detached comments) stay in place and create a new chunk.

File-header comments, i.e. comments at the very top of the file, are always treated as detached, even without a blank line. This preserves copyright notices and license headers.

The following code…

// Copyright notice and file header comment
import F from "f";
// Attached comment for `e`
import E from "e";
// Attached comment for `d`
import D from "d";
// Detached comment (new chunk)
// Attached comment for `b`
import B from "b";
// Attached comment for `a`
import A from "a";

…becomes:

// Copyright notice and file header comment
// Attached comment for `d`
import D from "d";
// Attached comment for `e`
import E from "e";
import F from "f";
// Detached comment (new chunk)
// Attached comment for `a`
import A from "a";
// Attached comment for `b`
import B from "b";

A blank line is automatically added after the header comment to ensure that the attached comment doesn’t merge with the header comment.

A source is split into segments by /. For example, src/file.js has two segments: src and file.js.

  • *: matches zero or more characters within a single segment; file.js matches *.js, but src/file.js does not.

  • **: matches zero or more segments and must be enclosed by / or be at the start/end; file.js and src/file.js both match **/*.js.

  • !: negates a pattern when used as the first character; file.js matches !*.test.js; Exceptions can be layered: ["@my/lib/**", "!@my/lib/internal/**", "@my/lib/internal/allowed/**"].

  • \*: matches a literal * character.

  • ?, [, ], {, }: reserved characters, must be escaped with \.