| name | home-manager-module |
| description | Write home-manager modules with options, config, and tests. Use when creating or modifying NixOS/home-manager modules, defining custom options, or generating test.nix files. |
Writing Home-Manager Modules
File Structure
A home-manager module lives in a directory with:
module-name/
├── default.nix # Module definition (options + config)
├── test.nix # Tests using pkgs.lib.runTests
Module Template
{ config, pkgs, lib, ... }:
let
cfg = config.<optionPath>;
in {
options.<optionPath> = lib.mkOption {
type = lib.types.listOf (lib.types.submodule {
options = {
myField = lib.mkOption {
type = lib.types.str;
description = "...";
};
optionalField = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = "...";
};
};
});
default = [];
description = "...";
};
config = lib.mkIf (cfg != []) {
# Transform cfg into actual home-manager config
};
}
Test Template
Tests use nix eval -f test.nix — success when output is [ ] (empty list).
let
pkgs = import <nixpkgs> {};
in pkgs.lib.runTests {
test-name = {
expr = <expression>;
expected = <expected value>;
};
}
Run tests with: nix eval -f test.nix
Testing Function-Style Modules
Modules that are functions (not traditional home-manager modules with options/config) can still be tested:
let
pkgs = import <nixpkgs> {};
in pkgs.lib.runTests {
test-module = pkgs.lib.testAllTrue [
(pkgs.lib.hasInfix "expected-string" content)
(pkgs.lib.hasInfix "another-string" content)
];
test-assertion = {
expr = builtins.tryEval (import ./default.nix { invalid-param = true; });
expected = { success = false; value = false; };
};
}
Where content is the output of the function, often obtained via builtins.readFile.
Common Patterns
- Use
pkgs.lib.filterAttrs (_: v: v != null) to drop unset optional fields before serialization
- Use
pkgs.lib.types.either pkgs.lib.types.str (pkgs.lib.types.listOf pkgs.lib.types.str) for fields accepting one or many strings
- Use
pkgs.lib.mkIf to conditionally apply config only when the option is non-empty
- Use
pkgs.lib.testAllTrue instead of manually comparing attrsets for simple boolean checks
- Use
pkgs.lib.hasInfix to check if a string contains a substring
- Use
builtins.tryEval to test code that should fail (e.g., assertion failures)
- Declare variables in the narrowest scope needed — avoid top-level declarations when only used in one test
- Reference example module at
~/.config/nixpkgs/usr/modules/yq-merge/
References
nixpkgs or pkgs source code: ~/.config/npins/nixpkgs/
lib source code: ~/.config/npins/nixpkgs/lib/
home-manager source code: ~/.config/npins/home-manager/