Parametric Aspects
What is a Parametric Aspect
Section titled “What is a Parametric Aspect”A parametric aspect is an aspect defined as a function whose arguments are
pipeline context values — host, user, home, or any custom entity kind.
Den introspects the function’s argument pattern and only calls it in contexts
where all required arguments are available.
# This aspect only activates in {host} contextsden.aspects.networking = { host, ... }: { nixos.networking.hostName = host.name;};
# This only activates when both {host, user} are presentden.aspects.user-groups = { host, user, ... }: { nixos.users.users.${user.userName}.extraGroups = [ "wheel" ];};
# This only activates for standalone {home} contextsden.aspects.shell-config = { home, ... }: { homeManager.programs.zsh.enable = true;};No wrapper needed — a bare function requiring { host, user } is silently
skipped in contexts that only have { host }. The argument shape is the
condition. No mkIf, no enable flags.
Static attrsets (non-functions) are always included regardless of context:
den.aspects.firewall = { nixos.networking.firewall.enable = true;};The binding rule
Section titled “The binding rule”When a parametric aspect at some scope S destructures an entity-kind arg
(host, user, home, …), exactly one of three things happens:
- In-context → bind once at S. If the kind is already in S’s context (e.g.
a
{ user, … }aspect included at a user scope), the arg binds and the aspect emits once, at S. - Schema-DAG descendant → fan out, emit at S. If the kind is a descendant
of S in the entity schema (e.g. a
{ user, … }aspect included at the host scope — users live under hosts), the aspect fans out once per matching descendant, each emitting class-locally at S. This is how a host-scope{ user, … }aspect produces per-user content on the host. - Neither → inert, silently. If the kind is neither in-context nor a descendant of S — including a misplaced arg, or any entity-kind arg at the root/flake scope — the aspect contributes nothing. There is no warning (a warning was considered and deliberately rejected: legitimate fan-out and misplacement are indistinguishable without whole-fleet context, so a warning would fire on correct code).
Where Parametric Args Work
Section titled “Where Parametric Args Work”Aspect-level parametric — the function wraps the whole aspect:
# host is a pipeline arg — the whole aspect is parametricden.aspects.laptop = { host, ... }: { nixos.networking.hostName = host.name; homeManager.programs.git.userName = host.hostName;};Class-level context injection — Den args inside a class module:
# host here is pre-applied by wrapClassModule, not parametric dispatchden.aspects.laptop = { nixos = { host, config, pkgs, ... }: { networking.hostName = host.name; };};Both patterns are valid and composable. Use aspect-level parametric when the context drives the entire aspect (which classes to include, what includes to add). Use class-level injection when only a specific class module needs entity data alongside module-system args.
See also
Section titled “See also”- Aspects & Functors — aspect structure and resolution
- Class Modules — flat-form context injection inside class modules
- Resolution Pipeline — how parametric dispatch fits the pipeline
- Deprecated den.lib functions — the old
den.lib.parametric/canTake/perHosthelpers the pipeline now replaces - Migrating from den.ctx — if upgrading from deprecated wrapper APIs