Skip to content

Without Flakes

new-scope creates an isolated aspect namespace within any lib.evalModules evaluation:

let
result = lib.evalModules {
modules = [
(new-scope "my")
{
my.aspects = { aspects, ... }: {
laptop = {
nixos = { pkgs, ... }: { environment.systemPackages = [ pkgs.vim ]; };
includes = [ aspects.base ];
};
base.nixos = { lib, ... }: { networking.hostName = "laptop"; };
};
}
];
};
in
lib.nixosSystem { modules = [ result.config.my.modules.nixos.laptop ]; }

new-scope "my" creates:

  • my.aspects — input: aspect definitions (type: aspectsType)
  • my.modules — output: transposed + resolved modules (read-only)

Independent scopes can coexist and merge. Each creates its own <name>.aspects / <name>.modules pair:

modules = [
(new-scope "foo")
(new-scope "bar")
{ foo.aspects.a.nixos.x = [ "from-foo" ]; }
{ bar.aspects.a.nixos.x = [ "from-bar" ]; }
({ config, ... }: { bar = config.foo; }) # merge foo into bar
];

After merging, bar.modules.nixos.a contains both "from-foo" and "from-bar".

new is the callback-based primitive underneath new-scope:

new (option: transposed: {
options.myLib.aspects = option;
config.myLib.modules = transposed;
}) config.myLib.aspects

new-scope is sugar for the common ${name}.aspects / ${name}.modules pattern. Use new directly when you need custom option paths or additional logic in the callback.

Useful for libraries that want isolated aspect scopes or flake-parts independence (see Den’s scope).

Contribute Community Sponsor