Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Fragment Directories

The .d pattern enables composable configuration where multiple sources contribute to a single output. This is particularly useful when external tools (like imp.gits) inject files into your project.

Auto-merged directories

Directories ending in .d that match known flake output names are automatically merged:

outputs/
  perSystem/
    packages.d/
      00-core.nix       # { default = myPkg; foo = fooPkg; }
      10-extras.nix     # { bar = barPkg; }
    devShells.nix

Result:

self'.packages = { default = myPkg; foo = fooPkg; bar = barPkg; }

Files are processed in sorted order (00 before 10) and merged with lib.recursiveUpdate. Later files can override or extend earlier ones.

Supported output names

Only these .d directories are auto-merged:

  • packages.d/
  • devShells.d/
  • checks.d/
  • apps.d/
  • overlays.d/
  • nixosModules.d/
  • homeModules.d/
  • darwinModules.d/
  • flakeModules.d/
  • nixosConfigurations.d/
  • darwinConfigurations.d/
  • homeConfigurations.d/
  • legacyPackages.d/

Manual fragment directories

Other .d directories are ignored by tree and consumed via imp.fragments:

{ pkgs, imp, ... }:
let
  shellHookFragments = imp.fragments ./shellHook.d;
in
{
  default = pkgs.mkShell {
    shellHook = shellHookFragments.asString;
  };
}

Common patterns:

DirectoryContentCollection method
shellHook.d/Shell scripts (.sh)imp.fragments.asString
env.d/Environment attrsetsimp.fragmentsWith.asAttrs

Merging base files with fragments

When both foo.nix and foo.d/ exist for a mergeable output, they are combined:

outputs/
  packages.nix        # { default = myPkg; }
  packages.d/
    10-extras.nix     # { bar = barPkg; }

Result:

self'.packages = { default = myPkg; bar = barPkg; }

The base file (foo.nix) is imported first, then fragments from foo.d/ are merged on top using lib.recursiveUpdate. This allows a base file to define core outputs while fragments extend them.

Fragment file structure

Each fragment in a .d directory can use the standard patterns:

# Simple attrset
{ default = myPkg; }

# Function receiving args
{ pkgs, ... }:
{ default = pkgs.hello; }

# With __inputs for flake input collection
{
  __inputs.foo.url = "github:owner/foo";
  __functor = _: { pkgs, foo, ... }: { default = foo.packages.${pkgs.system}.bar; };
}

Use case: injected dependencies

The .d pattern shines when external tools add files to your project. For example, with imp.gits syncing lintfra:

outputs/perSystem/
  packages.d/
    00-rust.nix         # Your rust packages
    10-lint.nix         # Injected by lintfra
  devShells.nix         # Your main devShell using inputsFrom
  devShells.d/
    10-lintfra.nix      # Injected lintfra devShell

Your packages.d/00-rust.nix defines the main packages. The injected 10-lint.nix adds a lint command. Both are merged into self'.packages without any manual wiring.

For devShells, the injected devShells.d/10-lintfra.nix provides a composable shell that your devShells.nix can consume via inputsFrom:

# devShells.nix
{ pkgs, self', ... }:
{
  default = pkgs.mkShell {
    inputsFrom = [ self'.devShells.lintfra ];  # from devShells.d/
    packages = [ /* your packages */ ];
  };
}

This uses the standard Nix inputsFrom pattern for shell composition.