Adding Modules
Creating a new module
Section titled “Creating a new module”1. Create the module file
Section titled “1. Create the module file”Create a new file in the appropriate directory:
modules/nixos/— System-level configurationmodules/home/— User-level configurationmodules/generic/— Shared between both
2. Write the module
Section titled “2. Write the module”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:
{ imports = [ # ... existing imports ./my-darwin-feature.nix ];}4. Define options
Section titled “4. Define options”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>:
{ lib, ... }:{ options.marchyo.myFeature = { enable = lib.mkOption { type = lib.types.bool; default = false; description = "Enable my feature"; }; };}5. Add a test
Section titled “5. Add a test”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:
{ helpers, ... }:let inherit (helpers) testNixOS withTestUser;in{ eval-my-feature = testNixOS "my-feature" (withTestUser { marchyo.myFeature.enable = true; });}Home Manager modules
Section titled “Home Manager modules”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 };}Best practices
Section titled “Best practices”- Use
lib.mkIffor feature-gated configuration blocks - Use
lib.mkDefaultfor values consumers should be able to override - Use
lib.mkMergewhen a module has multiple conditional branches - Define all options under
modules/nixos/options/(one file per namespace) — never definemarchyo.*options in individual modules - Add a test for every new feature flag