Submodule type as path to file: module system deep dive

I was working through the module system deep dive and noticed that the documentation for the submodule type

A set of sub options o. o can be an attribute set, a function returning an attribute set, or a path to a file containing such a value.

suggests a file path would work.

Changing working code from

    users = {
      alice = {
        departure.location = "San Francisco";
        arrival.location = "New York";
        pathStyle = {
          geodesic = true;
        };
      };
      bob = {
        departure.location = "New York";
        arrival.location = "Paris";
        pathStyle = {
          geodesic = true;
        };
      };
    };

to

users = ./users.nix;

and adding a file

# users.nix 
{
  alice = {
    departure.location = "San Francisco";
    arrival.location = "New York";
    pathStyle = {
      geodesic = true;
    };
  };
  bob = {
    departure.location = "New York";
    arrival.location = "Paris";
    pathStyle = {
      geodesic = true;
    };
  };
}

resulted in a type error, however: error: A definition for option 'users' is not of type 'attribute set of (submodule)' and I was wondering what I am doing wrong.

The deep dive documentation seems to be missing an example of a users config. I assume from the presentation a separate file is read into eval.nix or fed into eval.nix by the running nix-shell entr script, a step which might be nice to include in the documentation

Thanks!

Just as an addendum, in case anyone else runs into this, adding ./users.nix to eval.nix as an additional import works fine, that is:

# eval.nix
let
  nixpkgs = fetchTarball "https://github.com/NixOS/nixpkgs/tarball/nixos-25.11";
  pkgs = import nixpkgs { config = {}; overlays = []; };
in
pkgs.lib.evalModules {
  modules = [
    ({ config, ... }: { config._module.args = { inherit pkgs; }; })
    ./default.nix
    ./users.nix
  ];
}

with

# users.nix
{
  imports = [
    ./marker.nix
  ];

  users = {
    alice = {
      departure.location = "San Francisco";
      arrival.location = "New York";
      pathStyle = {
        geodesic = true;
      };
    };
    bob = {
      departure.location = "New York";
      arrival.location = "Paris";
      pathStyle = {
        geodesic = true;
      };
    };
  };
}

Although I am still curious about submodules as file paths

The tutorial tells you to add a users option with type lib.types.attrsOf userType, where userType is a submodule. That doesn’t make users a submodule! It makes users an attribute set of submodules.

(As the error message tells you: error: A definition for option 'users' is not of type 'attribute set of (submodule)'.)

So you can indeed use a file path for each individual user (alice = ./alice.nix;), just not for the set of users as a whole.


You can always define a set of options in another module that is imported via imports, as you’ve discovered. You can also always define a set of options in a Nix value that is directly imported from another file:

  users = import ./users.nix;

But be careful: when doing that, users.nix is not a module! You won’t be able to access config, lib, pkgs, or other module arguments unless you pass them explicitly to the value you import.

It’s a bit confusing that the word ‘import’ is used both by the Nix language’s import function and the imports attribute of the NixOS module DSL, because they’re quite different things! But both have their uses.

Ah, that makes sense.

In the live presentation, each user would ssh in to what I assume would be their own micro account to add their own file, and the hidden entr machinery would probably snarf them all up though some mechanism, possibly taking advantage of something like alice = ./alice.nix;or not