Why import-tree?
The Problem
Section titled “The Problem”As Nix configurations grow, the imports list becomes a maintenance burden:
# This doesn't scale.{ imports = [ ./modules/networking.nix ./modules/desktop/sway.nix ./modules/desktop/waybar.nix ./modules/services/docker.nix ./modules/services/ssh.nix ./modules/users/alice.nix # ... dozens more ];}Every new file requires updating the import list. Forget one and your config silently ignores it. Reorganize your directory and you must update every path by hand.
The Solution
Section titled “The Solution”{ imports = [ (import-tree ./modules) ];}Add a file to ./modules/ and it is automatically discovered. Reorganize freely. Use /_ prefixed directories for helpers that should not be imported.
Dendritic Pattern
Section titled “Dendritic Pattern”The Dendritic pattern — where each file is a self-contained module — was the original inspiration for import-tree.
With Dendritic, your configuration becomes a file tree — each concern in its own file, each file a module. import-tree removes the glue code that would otherwise connect them.
Beyond Loading Files
Section titled “Beyond Loading Files”import-tree is not just a file loader. Its builder API lets you:
- Filter which files are selected — by predicate, regex, or both.
- Transform discovered paths — wrap them in custom modules, read their contents, or anything else.
- Compose multiple directory trees with shared filters.
- Extend with domain-specific APIs — let library authors ship curated import-tree instances.
This makes import-tree useful for sharing pre-configured sets of modules across projects. Library authors can ship an import-tree instance with custom filters and API methods, and consumers pick what they need:
# A library could expose:lib.modules-tree = import-tree.addAPI { gaming = self: self.filter (lib.hasInfix "+gaming"); minimal = self: self.filterNot (lib.hasInfix "+heavy");};
# Consumers use it like:{ imports = [ lib.modules-tree.gaming.minimal ]; }Design Goals
Section titled “Design Goals”- Zero dependencies — a single
default.nix, no extra flake inputs - Works everywhere — flakes, non-flakes, any module system
- Composable — builder pattern with filter/map/extend chains
- Predictable — sensible defaults, clear ignore rules, no magic