Chapter 13: specialArgs and the Inputs Pipeline
This chapter explains how flake inputs travel from flake.nix into every module in your configuration. This is one of the concepts people find most confusing, so we will go through it step by step.
The Problem
Your modules need access to external flake inputs. For example, home/nimmo.nix needs to reference packages from claude-code-nix and the bash-it source. But how does a module deep in the file tree know about inputs declared in flake.nix?
The Solution: specialArgs
In flake.nix, the makeNixosSystem helper passes inputs through specialArgs for every host:
makeNixosSystem = { pkgs, hmFlake, configName, isServer }:
pkgs.lib.nixosSystem {
specialArgs = { inherit inputs; };
modules = [ ./hosts/${configName} ... ];
};
Let us unpack this line by line.
What specialArgs does
specialArgs is an attrset of extra arguments that NixOS passes to every module function. Normally, modules receive config, pkgs, lib, etc. With specialArgs = { inherit inputs; };, every module also receives inputs.
What inherit inputs means
Recall from Chapter 9:
{ inherit inputs; }
# is equivalent to:
{ inputs = inputs; }
The inputs variable comes from the outputs function parameter:
outputs = { self, nixpkgs, nixpkgs-unstable-small, nixpkgs-stable, home-manager,
claude-code-nix, codex-cli-nix, nixos-hardware, disko, bash-it, nur, sops-nix,
... }@inputs:
The @inputs binds the entire argument attrset to the name inputs. So inputs is an attrset containing:
{
self = <this flake>;
nixpkgs = <the nixpkgs flake>;
nixpkgs-unstable-small = <the unstable-small nixpkgs flake>;
nixpkgs-stable = <the stable nixpkgs flake>;
home-manager = <the home-manager flake>;
claude-code-nix = <the claude-code flake>;
codex-cli-nix = <the codex-cli flake>;
nixos-hardware = <the hardware flake>;
disko = <the disko flake>;
bash-it = <the bash-it source tree>;
nur = <the NUR flake>;
sops-nix = <the sops-nix flake>;
plasma-manager = <the plasma-manager flake>;
framework-system = <the Framework hardware tool flake>;
}
This entire attrset gets passed to every module (via specialArgs for NixOS modules, and via extraSpecialArgs for home-manager modules).
How Modules Receive It
Any module that needs inputs simply adds it to its function arguments:
{ config, pkgs, inputs, ... }:
Modules that do not need inputs do not list it, and the ... silently ignores it:
{ config, pkgs, ... }:
Which of your modules use inputs?
Let us check the key files:
| File | Uses inputs? |
Why? |
|---|---|---|
hosts/common/default.nix |
Yes | Imports inputs.disko.nixosModules.disko |
hosts/common/nix-settings.nix |
No | Only uses config and pkgs |
hosts/common/locale.nix |
No | Only uses config and pkgs |
hosts/electra/default.nix |
Yes | Imports inputs.nixos-hardware.nixosModules.* |
hosts/electra/hardware.nix |
No | Only uses config, pkgs, lib |
hosts/electra/hardware-dgpu.nix |
No | Only uses config, pkgs, lib |
hosts/lena/default.nix |
Yes | Imports inputs.nixos-hardware.nixosModules.* |
home/nimmo.nix |
Yes | References inputs.claude-code-nix, inputs.bash-it, etc. |
users/nimmo.nix |
No | Just account definition (groups only) |
Most modules in modules/ |
No | Most do not need external flake packages |
Note that home/nimmo.nix receives inputs through a different mechanism than NixOS modules. While NixOS modules get inputs via specialArgs, home-manager modules get it via extraSpecialArgs (set in flake.nix):
home-manager.extraSpecialArgs = {
inherit inputs;
isServer = isServer;
flakeRepo = config.nixosConfig.flakeRepo;
primaryUser = config.nixosConfig.primaryUser;
userEmail = config.nixosConfig.userEmail;
};
The additional parameters (isServer, flakeRepo, primaryUser, userEmail) allow home/nimmo.nix to adjust behaviour per host. isServer is the most visible: it gates desktop-only packages.
The Flow, End to End
Here is the complete pipeline for how claude-code-nix gets from a GitHub repository to being installed in nimmo's PATH:
1. flake.nix declares the input:
inputs.claude-code-nix.url = "github:sadjow/claude-code-nix";
2. flake.lock pins it to a specific revision:
"rev": "84d6591a3547c45ee177131aa09a64caefcf5c14"
3. The outputs function receives it:
outputs = { ..., claude-code-nix, ... }@inputs:
4. makeNixosSystem passes inputs and isServer to home-manager modules:
home-manager.extraSpecialArgs = { inherit inputs; isServer = isServer; ... };
5. home/nimmo.nix (or a profile module) receives inputs and uses it:
{ config, pkgs, inputs, ... }:
{
home.packages = [
inputs.claude-code-nix.packages.${pkgs.stdenv.hostPlatform.system}.claude-code
];
}
6. Home-manager evaluates this, downloads/builds the package, and puts it in nimmo's PATH.
Every external package follows this same pipeline. The steps are:
- Declare the input in
flake.nix - Let
flake.lockpin it - It arrives in the
outputsfunction parameter specialArgs(for NixOS modules) orextraSpecialArgs(for home-manager modules) passes it through- A module references it via
inputs.<name>
Using specialArgs for Other Things
While your configuration only passes inputs, you could pass anything through specialArgs:
electra = nixpkgs.lib.nixosSystem {
specialArgs = {
inherit inputs;
myCustomValue = "hello";
featureFlags = { experimentalUI = true; };
};
modules = [ ./hosts/electra ];
};
Then in any module:
{ config, pkgs, myCustomValue, featureFlags, ... }:
{
# myCustomValue is "hello"
# featureFlags.experimentalUI is true
}
This can be useful for passing host-specific configuration values without hardcoding them in modules. However, for most cases, the NixOS options system (Chapter 12) is a better approach.
Common Mistakes
Forgetting inputs in the function arguments
If a module uses inputs but does not list it:
{ config, pkgs, ... }: # Missing inputs!
{
users.users.nimmo.packages = [
inputs.claude-code-nix.packages.${pkgs.stdenv.hostPlatform.system}.claude-code
# Error: undefined variable 'inputs'
];
}
The fix: add inputs to the argument list:
{ config, pkgs, inputs, ... }:
Using inputs in a module that is not passed specialArgs
If you use nixpkgs.lib.nixosSystem without specialArgs, inputs is not available:
electra = nixpkgs.lib.nixosSystem {
# No specialArgs!
modules = [ ./hosts/electra ];
};
This would break any module that tries to use inputs. Always include specialArgs = { inherit inputs; };.
Confusing pkgs with inputs.nixpkgs
pkgs is the evaluated package set -- you use it to get packages: pkgs.firefox, pkgs.git.
inputs.nixpkgs is the nixpkgs flake itself -- you use it for things like nixpkgs.lib.nixosSystem.
They come from the same source, but pkgs has been "instantiated" with your system's platform and configuration (like allowUnfree), while inputs.nixpkgs is the raw flake.