Skip to content

Creating Modules

Learn how to write custom Nix modules for your configuration.



Modules are reusable configuration units in Nix. They:

  • Encapsulate related configuration
  • Provide options for customization
  • Can be composed with other modules
  • Enable declarative system configuration

A module is a Nix function that returns an attribute set with configuration:

{ config, pkgs, lib, ... }:
{
# Configuration goes here
options = { }; # Define options
config = { }; # Set values
imports = [ ]; # Import other modules
}

Modules receive these standard arguments:

  • config - The full system configuration
  • pkgs - Package set (nixpkgs)
  • lib - Nixpkgs library functions
  • ... - Other custom arguments

{ config, pkgs, lib, ... }:
{
# Import other modules
imports = [
./submodule.nix
];
# Define options
options = {
services.myservice.enable = lib.mkEnableOption "my service";
};
# Configuration (conditional on options)
config = lib.mkIf config.services.myservice.enable {
environment.systemPackages = [ pkgs.mypackage ];
};
}
nix/modules/
├── common.nix # Cross-platform base
├── darwin-base.nix # macOS base
├── darwin/
│ └── homebrew.nix # Homebrew config
├── cloud/
│ ├── ec2-base.nix # AWS config
│ └── gce-base.nix # GCP config
└── secrets/
└── sops.nix # SOPS config

Create nix/modules/development.nix:

{ config, pkgs, lib, ... }:
{
# System packages for development
environment.systemPackages = with pkgs; [
git
vim
curl
wget
];
# Git configuration
programs.git = {
enable = true;
config = {
init.defaultBranch = "main";
pull.rebase = false;
};
};
# Shell configuration
programs.zsh.enable = true;
}

Add to your host config or flake.nix:

{
imports = [
./nix/modules/development.nix
];
}

Options make modules configurable:

{ config, pkgs, lib, ... }:
{
options.myapp = {
enable = lib.mkEnableOption "MyApp service";
port = lib.mkOption {
type = lib.types.port;
default = 8080;
description = "Port to listen on";
};
package = lib.mkOption {
type = lib.types.package;
default = pkgs.myapp;
description = "MyApp package to use";
};
dataDir = lib.mkOption {
type = lib.types.str;
default = "/var/lib/myapp";
description = "Data directory";
};
};
config = lib.mkIf config.myapp.enable {
# Use options here
environment.systemPackages = [ config.myapp.package ];
# Create data directory
system.activationScripts.myapp = ''
mkdir -p ${config.myapp.dataDir}
'';
};
}

Common option types:

{
options = {
# Boolean
enable = lib.mkEnableOption "feature";
# String
name = lib.mkOption {
type = lib.types.str;
default = "default";
};
# Integer
count = lib.mkOption {
type = lib.types.int;
default = 1;
};
# Port (integer 1-65535)
port = lib.mkOption {
type = lib.types.port;
default = 8080;
};
# Path
configFile = lib.mkOption {
type = lib.types.path;
default = ./config.yaml;
};
# Package
package = lib.mkOption {
type = lib.types.package;
default = pkgs.myapp;
};
# List of strings
hosts = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
};
# Attribute set
settings = lib.mkOption {
type = lib.types.attrs;
default = { };
};
# Enum (one of specific values)
logLevel = lib.mkOption {
type = lib.types.enum [ "debug" "info" "warn" "error" ];
default = "info";
};
};
}
# In your host config
{
myapp = {
enable = true;
port = 3000;
dataDir = "/custom/path";
};
}

{ config, pkgs, lib, ... }:
{
config = lib.mkIf config.services.myapp.enable {
# Only applied if myapp is enabled
environment.systemPackages = [ pkgs.myapp ];
# Nested conditional
environment.variables = lib.mkIf config.services.myapp.debug {
DEBUG = "true";
};
};
}
{ config, pkgs, lib, ... }:
{
config = lib.mkMerge [
# Base config (always applied)
{
environment.systemPackages = [ pkgs.base ];
}
# Conditional config
(lib.mkIf config.myapp.enable {
environment.systemPackages = [ pkgs.myapp ];
})
# Another conditional
(lib.mkIf config.myapp.extraFeatures {
environment.systemPackages = [ pkgs.extra ];
})
];
}
{ config, pkgs, lib, ... }:
{
config = lib.mkMerge [
# Common config
{
environment.systemPackages = [ pkgs.common ];
}
# macOS only
(lib.mkIf pkgs.stdenv.isDarwin {
environment.systemPackages = [ pkgs.darwin-only ];
})
# Linux only
(lib.mkIf pkgs.stdenv.isLinux {
environment.systemPackages = [ pkgs.linux-only ];
})
];
}
{ config, pkgs, lib, ... }:
{
config = {
# Fail if condition not met
assertions = [
{
assertion = config.myapp.enable -> config.database.enable;
message = "MyApp requires database to be enabled";
}
];
# Warn user
warnings = lib.optional
(config.myapp.port < 1024)
"MyApp running on privileged port ${toString config.myapp.port}";
};
}

Terminal window
# Test without applying
darwin-rebuild build --flake .#your-hostname
# Check what would change
darwin-rebuild build --flake .#your-hostname
nix store diff-closures /run/current-system ./result
Terminal window
# Check for syntax errors
nix flake check
# Evaluate module
nix eval .#darwinConfigurations.your-hostname.config.myapp.enable
Terminal window
# Show all options for a module
darwin-option myapp
# Show option value
darwin-option myapp.enable
# Show option documentation
darwin-option -r myapp

# ✅ Good: Clear structure
{ config, pkgs, lib, ... }:
let
cfg = config.services.myapp;
in {
options.services.myapp = {
enable = lib.mkEnableOption "MyApp";
# ... more options
};
config = lib.mkIf cfg.enable {
# Use cfg instead of config.services.myapp
environment.systemPackages = [ cfg.package ];
};
}
# ✅ Good: Hierarchical, descriptive
options.services.myapp = {
enable = ...;
settings.port = ...;
settings.host = ...;
};
# ❌ Bad: Flat, unclear
options.myapp-enabled = ...;
options.myapp-port = ...;
{
options.myapp.enable = lib.mkOption {
type = lib.types.bool;
default = false;
description = lib.mdDoc ''
Whether to enable MyApp service.
MyApp provides XYZ functionality.
See <https://myapp.example.com> for details.
'';
};
}
# ✅ Good: Sensible defaults
options.myapp = {
port = lib.mkOption {
type = lib.types.port;
default = 8080; # Standard port
};
logLevel = lib.mkOption {
type = lib.types.enum [ "debug" "info" "warn" "error" ];
default = "info"; # Reasonable default
};
};

{ config, pkgs, lib, ... }:
let
cfg = config.services.myservice;
in {
options.services.myservice = {
enable = lib.mkEnableOption "my service";
port = lib.mkOption {
type = lib.types.port;
default = 3000;
description = "Service port";
};
user = lib.mkOption {
type = lib.types.str;
default = "myservice";
description = "User to run service as";
};
};
config = lib.mkIf cfg.enable {
# Create user
users.users.${cfg.user} = {
name = cfg.user;
description = "MyService user";
};
# Install package
environment.systemPackages = [ pkgs.myservice ];
# Create launchd service (macOS)
launchd.user.agents.myservice = lib.mkIf pkgs.stdenv.isDarwin {
config = {
ProgramArguments = [
"${pkgs.myservice}/bin/myservice"
"--port" "${toString cfg.port}"
];
RunAtLoad = true;
};
};
};
}
{ config, pkgs, lib, ... }:
let
cfg = config.development;
in {
options.development = {
enable = lib.mkEnableOption "development tools";
languages = lib.mkOption {
type = lib.types.listOf (lib.types.enum [ "python" "node" "go" "rust" ]);
default = [ ];
description = "Programming languages to install";
};
};
config = lib.mkIf cfg.enable (lib.mkMerge [
# Base development tools
{
environment.systemPackages = with pkgs; [
git
vim
curl
jq
];
}
# Python
(lib.mkIf (builtins.elem "python" cfg.languages) {
environment.systemPackages = with pkgs; [
python3
python3Packages.pip
python3Packages.virtualenv
];
})
# Node.js
(lib.mkIf (builtins.elem "node" cfg.languages) {
environment.systemPackages = with pkgs; [
nodejs
nodePackages.npm
nodePackages.pnpm
];
})
# Go
(lib.mkIf (builtins.elem "go" cfg.languages) {
environment.systemPackages = [ pkgs.go ];
})
# Rust
(lib.mkIf (builtins.elem "rust" cfg.languages) {
environment.systemPackages = with pkgs; [
rustc
cargo
rustfmt
];
})
]);
}
{ config, pkgs, lib, ... }:
let
cfg = config.cloud;
in {
options.cloud = {
providers = lib.mkOption {
type = lib.types.listOf (lib.types.enum [ "aws" "gcp" "azure" ]);
default = [ ];
description = "Cloud providers to install tools for";
};
};
config = {
environment.systemPackages = lib.flatten [
# AWS
(lib.optional (builtins.elem "aws" cfg.providers) pkgs.awscli2)
# GCP
(lib.optional (builtins.elem "gcp" cfg.providers) pkgs.google-cloud-sdk)
# Azure
(lib.optional (builtins.elem "azure" cfg.providers) pkgs.azure-cli)
# Common tools if any provider enabled
(lib.optionals (cfg.providers != [ ]) [
pkgs.kubectl
pkgs.terraform
])
];
};
}




Ready to create modules? Start with a simple module!