apotheke

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.

Wrong — Forms never matches
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:

Right — specific before general
groups: [
    { name: 'Forms', match: ['react-hook-form', 'zod'] },
    { name: 'React', match: ['react', 'react-*'] }
]

or narrow the greedy pattern:

Also right — no wildcard
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.

PatternMatches
reactexactly 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:

Before
import { useState } from 'react';
import { useEffect } from 'react';

becomes

After
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 groups
  • groupComments — a // GroupName header above each group

Set groupComments to false if you want the ordering without the comment noise.

On this page