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.

Every entity (host, user, home) has a policies option — a list of policy names to activate when that entity is resolved. Core policies (those marked _core = true) are always active regardless.

{
# Activate a policy for all hosts of this kind
den.schema.host.policies = [ "host-to-peers" ];
# Activate a policy for a specific host
den.hosts.x86_64-linux.igloo.policies = [ "microvm-guests" ];
}

See Policies 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 to require that all entity attributes be declared via schema options:

{
# Enable strict mode for all hosts
den.schema.host.strict = true;
# Now this would be an error — gpu is not a declared option:
# den.hosts.x86_64-linux.igloo.gpu = "nvidia";
}

Strict mode is useful for larger configurations where you want to catch typos and enforce a consistent entity interface. Undeclared attributes produce an eval error with a message indicating which option is missing.

Contribute Community Sponsor