Skip to content

API Reference

As a flake input:

inputs.import-tree
inputs.import-tree.url = "github:denful/import-tree";

As a plain import:

let import-tree = import ./path-to/import-tree;

The resulting value is a callable attrset - the primary import-tree object.


Takes a path or a (nested) list of paths. Returns a Nix module with imports set to all discovered files.

import-tree ./modules
import-tree [ ./modules ./extra ]
import-tree [ ./a [ ./b ] ] # nested lists are flattened

Other import-tree objects can appear in the list as if they were paths.

Anything with an outPath attribute (like flake inputs) is treated as a path:

import-tree [ { outPath = ./modules; } ]

Non-path values (like attrsets) are passed through the filter and included if they pass.


fn : string -> bool - only include paths where fn returns true.

import-tree.filter (lib.hasInfix ".mod.") ./modules

Multiple .filter calls compose with AND.

Inverse of .filter - exclude paths where fn returns true.

import-tree.filterNot (lib.hasInfix "experimental") ./modules

Include only paths matching the regex. Uses builtins.match (tests full string).

import-tree.match ".*/[a-z]+_[a-z]+\.nix" ./modules

Multiple .match calls compose with AND.

Exclude paths matching the regex.

import-tree.matchNot ".*/test_.*\.nix" ./modules

Replaces the default filter (.nix suffix, no /_ infix). Use for non-Nix files or custom ignore conventions.

import-tree.initFilter (lib.hasSuffix ".md") ./docs
import-tree.initFilter (p: lib.hasSuffix ".nix" p && !lib.hasInfix "/skip/" p)

Also applies to non-path items in import lists.


fn : path -> a - transform each discovered path.

import-tree.map lib.traceVal ./modules # trace each path
import-tree.map (p: { imports = [ p ]; }) # wrap in module
import-tree.map import # actually import

Multiple .map calls compose (first map runs first).


Prepend a path to the internal path list. Can be called multiple times:

(import-tree.addPath ./vendor).addPath ./modules
# discovers files in both directories

Extend the import-tree object with new methods. Each value is a function receiving self (the current import-tree):

import-tree.addAPI {
maximal = self: self.addPath ./all-modules;
feature = self: name: self.filter (lib.hasInfix name);
}

Methods are late-bound - you can reference methods added in later .addAPI calls.

Add attributes to the scope passed to imported modules. Enables direct access to custom variables inside imports:

import-tree.addScoped { foo = 42; mylib = lib; } ./modules
# modules can now use `foo` and `mylib` directly

The provided attributes are merged into the scope used by builtins.scopedImport:

# module.nix - can reference scoped values directly
{
config.myValue = foo;
config.libResult = mylib.version;
}

Multiple .addScoped calls accumulate:

import-tree (it: it.addScoped { foo = 42 }) (it: it.addScoped { bar = 99 })
# modules can access both foo and bar

Required before .leafs or .pipeTo when used outside module evaluation. Provides lib.filesystem.listFilesRecursive.

import-tree.withLib pkgs.lib

Returns a configured import-tree that produces file lists instead of modules:

(import-tree.withLib lib).leafs ./modules
# => [ ./modules/a.nix ./modules/b.nix ]

Shorthand for .leafs.result:

(import-tree.addPath ./modules).withLib lib |>.files

Like .leafs but pipes the result list through fn:

(import-tree.withLib lib).pipeTo builtins.length ./modules
# => 3

Evaluate with an empty path list. Equivalent to calling with []:

(import-tree.addPath ./modules).result

Returns a fresh import-tree with empty state - no paths, filters, maps, or API extensions.

configured-tree.new # back to a clean slate
Contribute Community Sponsor