Configuring groups
How matching, ordering, sorting and deduplication actually behave.
A group is a name and a list of glob patterns:
{ name: 'Api', match: ['**/api/**', '@tanstack/react-query', 'axios'] }Every import is tested against each group in array order, and the first match wins. The output groups then appear in that same order, so the config file reads exactly like the top of your files.
Ordering is the whole design
Because the first match wins, a broad pattern early in the array will swallow imports you meant for a later group.
groups: [
{ name: 'React', match: ['react', 'react-*'] },
{ name: 'Forms', match: ['react-hook-form'] } // unreachable
]react-* already claimed react-hook-form. Two ways out — put the specific
group first:
groups: [
{ name: 'Forms', match: ['react-hook-form', 'zod'] },
{ name: 'React', match: ['react', 'react-*'] }
]or narrow the greedy pattern:
groups: [
{ name: 'React', match: ['react', 'react-dom'] },
{ name: 'Forms', match: ['react-hook-form', 'zod'] }
]What you can match
Patterns are globs tested against the import specifier.
| Pattern | Matches |
|---|---|
react | exactly react |
react-* | react-dom, react-hook-form, … |
@tanstack/* | any single segment under the scope |
better-auth/** | any depth of subpath |
**/hooks/** | relative or aliased paths containing a hooks dir |
node:* | Node builtins written with the node: prefix |
Path patterns work on relative imports (../hooks/use-user) and on aliased
ones (@/hooks/use-user) — see aliases.
The implicit Others group
Imports matching no group collect in a trailing group named Others. You do not declare it and cannot lose an import by forgetting a pattern. If Others is large, that is a signal your config is under-described.
Side-effect imports
Bare imports with no bindings — import './styles.css' — are left exactly
where you wrote them, and nothing moves across them. Their position is
load-bearing: the CSS cascade resolves equal-specificity rules by source order,
and a polyfill has to be evaluated before the code that relies on it.
apotheke reads one file at a time, so it cannot see through a specifier. A value
import may reach stylesheets of its own — import App from './App' can pull in
every component stylesheet in the app — and apotheke has no way to know. Moving
a bare import above such an import would reorder effects it cannot see, so it
does not.
A bare import therefore acts as a barrier, and value imports are grouped and sorted only among themselves within the span between two barriers:
// Before
import App from './App';
import './themes/index.css';
import { createRoot } from 'react-dom/client';
// After — App keeps its place ahead of the stylesheet
// Internal
import App from './App';
import './themes/index.css';
// React
import { createRoot } from 'react-dom/client';A run of bare imports carries no // header, since it is not a group you
declared. In the common case — every stylesheet imported at the top of an entry
file — nothing precedes the first barrier, so the result reads exactly as you
would expect: bare imports first, your groups underneath.
Sorting within a group
Within each group, imports are sorted alphabetically by specifier. Named imports inside a single statement are sorted alphabetically too, with inline type specifiers floating ahead of value ones. Both behaviours are unconditional — there is no option to disable them.
Type-only imports float to the top of their group:
import type { ApothekeConfig } from './types';
import { formatImports } from './format';Deduplication
Two statements importing from the same specifier are merged into one:
import { useState } from 'react';
import { useEffect } from 'react';becomes
import { useEffect, useState } from 'react';Value and type imports of the same specifier stay separate, because collapsing
them changes meaning under verbatimModuleSyntax:
import type { FC } from 'react';
import { useState } from 'react';Presentation
Two booleans control the cosmetics, both defaulting to true:
groupSeparator— blank line between groupsgroupComments— a// GroupNameheader above each group
Set groupComments to false if you want the ordering without the comment
noise.
