Skip to content

Adding Modules

Create a new file in the appropriate directory:

  • modules/nixos/ — System-level configuration
  • modules/home/ — User-level configuration
  • modules/generic/ — Shared between both

Follow the standard module structure:

{ config, lib, pkgs, ... }:
let
cfg = config.marchyo;
in
{
config = lib.mkIf cfg.myFeature.enable {
# Your configuration here
environment.systemPackages = with pkgs; [
some-package
];
};
}

3. No import registration needed (usually)

Section titled “3. No import registration needed (usually)”

modules/nixos/ and modules/home/ are auto-discovered via lib/discover-modules.nix — every .nix file in the directory (plus any subdirectory containing a default.nix) is imported automatically, so adding a module is a one-file change.

The one hand-curated exception is modules/darwin/default.nix: it holds a curated darwin-safe subset (Wayland/systemd/desktop modules are NixOS-only), so darwin modules must be added to its import list manually:

modules/darwin/default.nix
{
imports = [
# ... existing imports
./my-darwin-feature.nix
];
}

Add any new options in a file under the modules/nixos/options/ directory (one file per logical namespace, auto-discovered — use an existing namespace file or create a new one) declaring options.marchyo.<namespace>:

modules/nixos/options/my-feature.nix
{ lib, ... }:
{
options.marchyo.myFeature = {
enable = lib.mkOption {
type = lib.types.bool;
default = false;
description = "Enable my feature";
};
};
}

Add an evaluation test in tests/eval/<feature>.nix (auto-discovered) — each file is a function receiving the shared helpers and returning an attrset of named tests:

tests/eval/my-feature.nix
{ helpers, ... }:
let
inherit (helpers) testNixOS withTestUser;
in
{
eval-my-feature = testNixOS "my-feature" (withTestUser {
marchyo.myFeature.enable = true;
});
}

Home Manager modules that need NixOS config use osConfig:

{ config, lib, osConfig ? {}, ... }:
let
cfg = osConfig.marchyo or {};
in
{
config = lib.mkIf (cfg ? myFeature && cfg.myFeature.enable) {
# User-level configuration
};
}
  • Use lib.mkIf for feature-gated configuration blocks
  • Use lib.mkDefault for values consumers should be able to override
  • Use lib.mkMerge when a module has multiple conditional branches
  • Define all options under modules/nixos/options/ (one file per namespace) — never define marchyo.* options in individual modules
  • Add a test for every new feature flag