Skip to content

Entities & Schema

An entity is a typed data record — a host, a user, or a standalone home. Entities declare what exists in your infrastructure. They carry metadata consumed by policies (which define relationships) and aspects (which define behavior).

Den provides three built-in entity types:

  • den.hosts — machines, keyed by <system>.<name>. Each host produces a nixosConfigurations, darwinConfigurations, or systemConfigs flake output depending on its class (auto-detected from the system platform).

  • den.homes — standalone home-manager configurations for systems without root access, keyed by <system>.<name>. Produces homeConfigurations outputs.

  • Users — declared inline on a host via den.hosts.<sys>.<host>.users.<name>. Users are not top-level entities; they live within their host.

{
den.hosts.x86_64-linux.igloo.users = {
alice = { };
bob = { };
};
den.homes.aarch64-darwin.alice = { };
}

Every entity carries a class that determines how it is instantiated and where its output lands. For hosts this is "nixos" (auto-detected from x86_64-linux / aarch64-linux) or "darwin" (from *-darwin); for homes it is "homeManager". Override with class = "darwin" if auto-detection is wrong. Each entity also has an aspect field that points to the aspect responsible for configuring it — by default den.aspects.<name>.

Users have a classes list declaring which home-environment classes they want to use (e.g., classes = [ "homeManager" ]).

den.schema lets you define options that apply to every entity of a given kind. There are three built-in schema kinds — host, user, and home — plus conf which is shared across all three.

{
# Enable home-manager integration for all hosts
den.schema.host.home-manager.enable = true;
# Add a custom option to every user
den.schema.user = { user, lib, ... }: {
options.groupName = lib.mkOption { default = user.userName; };
};
# Shared across all entity kinds
den.schema.conf = {
options.copyright = lib.mkOption { default = "Copy-Left"; };
};
}

Schema entries are deferredModule values — they can be plain attrsets with options/config or functions that receive the entity’s module arguments.

Each schema kind name (excluding conf and internal prefixes) is treated as a first-class entity kind. Entity kinds automatically receive:

  • A resolved attribute containing the fully resolved aspect output for that entity.
  • An id_hash for safe entity comparison (Nix’s == is fragile across module boundaries).
  • Context arguments derived from the entity’s module args, filtered to known kinds.

This is how the Data concern feeds into the resolution pipeline — entities declare what they are, and policies determine where behavior binds.

Policies are declared in the den.policies registry and activated by placing them into an includes list. To activate a policy for every entity of a kind, add it to that kind’s schema includes:

{
# Activate a policy for every host
den.schema.host.includes = [ den.policies.host-to-peers ];
}

This is the same includes mechanism aspects use — schema includes simply attaches the policy to every entity of that kind. See Policies and Policy Activation for how activation is used during resolution.

Host, user, and home types all use freeformType, so you can attach arbitrary data to any entity without declaring options first:

den.hosts.x86_64-linux.igloo = {
gpu = "nvidia";
datacenter = "eu-west";
};

These attributes are accessible in aspects via the entity’s context argument:

den.aspects.igloo.includes = [
(
{ host, ... }:
lib.optionalAttrs (host ? gpu) {
nixos.hardware.nvidia.enable = true;
}
)
];

For attributes that should exist on every entity of a kind with a default value, prefer defining them in den.schema instead — this gives you type checking and documentation. See the Schema reference for the full option list, or the Declare Hosts & Users guide for practical examples.

By default, Den allows freeform attributes on entities. Enable strict mode by importing the strict flake module, which requires that all entity attributes be declared via schema options:

{
imports = [ inputs.den.flakeModules.strict ];
# Now this would be an error — gpu is not a declared option:
# den.hosts.x86_64-linux.igloo.gpu = "nvidia";
}

The module applies strict typing to the host, user, home, aspect, and flake schema kinds. Strict mode is useful for larger configurations where you want to catch typos and enforce a consistent entity interface. Setting an undeclared attribute throws a STRICT MODE error naming the missing option.

Contribute Community Sponsor