No description
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-10-10 19:59:14 +01:00
nix add other flake output kinds 2026-10-10 19:58:39 +01:00
tests allow passing in the flake inputs 2026-10-10 19:58:23 +01:00
flake.lock first working-ish version 2026-10-09 20:27:11 +01:00
flake.nix fix tests 2026-10-10 19:03:51 +01:00
LICENSE Initial commit 2026-10-06 19:38:04 +02:00
README.md add readme with basic instructions 2026-10-10 19:59:14 +01:00

This is my take on a module system used to simplify the definition of a Nix flake. flake-parts exists to do basically the same thing, and you should check it out, before using this flake.

Quick start

Add to your flake input:

# flake.nix

inputs.modularFlake.url = "https://git.dzuchun.ing/dzu/modular-flake.git";
# make it follow your nixpkgs, if you want

Use in your flake output:

# flake.nix
outputs = { modularFlake, ... }: modularFlake.mkFlake { };

Well, the code above actually produces an empty flake. For most use-cases, you would have to at least provide it with one system to use:

# flake.nix
outputs = { modularFlake, ... }: modularFlake.mkFlake { systems = [ "x86_64-linux" ] };

Defining stuff

You can export things from your flake, using nix modules. Nix module is

  • an attrset of a specific form, or
  • a lambda evaluating to such attrset, or
  • a path to a .nix file, containing either of those

You should emit your outputs in the config attribute, and as-if <system> component of the path does not exist.

Here is a module, defining a check called mathWorks:

# math-check.nix
{
    config.checks.mathWorks = 
        assert 2 + 2 == 4;
        null;
}

Note, that unlike in flake output checks attribute, you may emit any kind of expressions -- for checks, they will be force-evaluated/built.

Here is how you can define an app:

# meow-app.nix
{ lib, hasSystem, pkgs }: 
lib.optionalAttrs hasSystem {
    config.apps.meow.program = pkgs.writeShellScript "${pkgs.coreutils}/bin/echo 'meow'";
}

And here is an overlay:

# my-overlay.nix
_: {
    config.overlays.myOverlay = final: prev: { /* whatever you like */ };
}

To apply your flake modules to your flake output, put them into modules argument of mkFlake:

# flake.nix
outputs = { modularFlake, ... }: modularFlake.lib.mkFlake {
    modules = [
        ./math-check.nix
        ./meow-app.nix
        ./my-overlay.nix
    ];
};

Defining ways to define stuff

But what if you don't want to use standard flake outputs? What if you want to define your own outputs, with their own schemas and stuff?

But what do you do, if your outputs conflict with the defaults?

You define your own outputs, of course!

# cats-output.nix
{ lib, ... }:
let
petCats =
  {
    lib,
    pkgs,
    cats,
  }:
  pkgs.writeShellScript "pet-cats" (
    lib.concatStringsSep "\n" (
      map (
        cat:
        if cat == "niko" then
          builtins.warn "niko is not a cat!!!" ""
        else
          "${pkgs.coreutils}/bin/printf 'cat %s has been petted!' ${lib.escapeShellArg cat}"
      ) (lib.unique (lib.sort (a: b: a < b) cats))
    )
  );
in
{
  config.outputKinds = [
    (_: {
      options = with lib; {
        cats = mkOption {
          type = with types; listOf str;
          description = "a list of cats to pet";
        };
      };
      outputs =
        {
          lib,
          hasSystem,
          config,
          system,
          pkgs,
          ...
        }:
        lib.optionalAttrs hasSystem {
          apps.${system}.catPetters = {
            type = "app";
            program = petCats {
              cats = config.cats;
              inherit lib pkgs;
            };
          };
        };
    })
  ];
};

You can now pet cats, in the modular way:

# cat-definitions.nix
{
    config = {
        cats = [ "orange" "pink" ];
    };
}
# niko-definition.nix
{
    config = {
        # TODO: add cuteness check

        cats = [ "niko" ]; # uh oh
    };
}
# flake.nix
outputs = { modularFlake, ... }: modularFlake.lib.mkFlake {
    outputs = [
        ./cats-output.nix
    ];
    modules = [
        ./cat-definitions.nix
        ./niko-definition.nix
    ];
};

Note, that outputs parameter defines a complete set of outputs you use. So (for example) any checks you emmit with the code above will be ignored.

Using default outputs

Default output definitions are available under modularFlake.defaultOutputs. Here is how you would subset your flake outputs to "apps only":

# flake.nix
outputs = { modularFlake, ... }: modularFlake.lib.mkFlake {
    outputs = with modularFlake.defaultOutputs; [ apps ];
    ...
};

Of course, you can combine default outputs with your own:

# flake.nix
outputs = { modularFlake, ... }: modularFlake.lib.mkFlake {
    outputs = with modularFlake.defaultOutputs; [
        apps
        ./cats-output.nix
    ];
    ...
};

Using other flake inputs

In case you need to use some other flake input in your module definition, and want to keep the module system ergonomics, here is the way:

# flake.nix
outputs = { modularFlake, ... }@flakeInputs: modularFlake.lib.mkFlake {
    inherit flakeInputs;
    ...
};

All of your modules will now see your flake inputs at flakeInputs argument.