From our perspective here at Determinate Systems, the primary benefit of flake.lock file, providing an essential bedrock for making Nix
But when it comes to secondary benefits, flakes offer a pretty great one in the form of enhanced discoverability for Nix expressions. Today, we’re excited to announce the release of flake schemas, a new feature in Determinate Nix that provides a much-needed enhancement to this discoverability. With flake schemas, flake outputs are now fully customizable and extensible, enabling you to define output types that fit any Nix use case imaginable.
Some background
Nix with flakes originally had a built-in set of recognized packages, devShells, and formatter, and it mapped common Nix commands to specific output types, most notably nix build to packages, nix develop to devShells, nix fmt to formatter, and nix run to apps.
This system provided an immediate upgrade from the pre-flake convention of using files like default.nix, shell.nix, and release.nix to convey intent and brought some much-needed regularity to the ecosystem.
Suddenly you had the nix flake show command at your disposal to show you what a flake had to offer—rather than loose norms and guesswork—and as a result Nix developers were able to build powerful new workflows around flakes.
But the built-in output types were, naturally, not particularly great at handling outputs that don’t fit the mold, leaving Nix developers to resort to practices like putting things that aren’t really standard packages, such as OCI images or IDE plugins, under packages so that Nix could at least “see” them, or, worse, using more descriptive names but accepting that Nix would register those non-standard output types as an enigmatic unknown.
So we opted to break the impasse and build something better.
Introducing flake schemas
A while back, we at Determinate Systems decided that we wanted to go beyond the built-in output types and introduced flake schemas in a branch of the Determinate Nix CLI. You no longer need to use this branch because they are available in Determinate Nix 3.17.0 (and later).
Let’s start with a flake with standard outputs, our DeterminateSystems/flake-checker
nix flake show "https://flakehub.com/f/DeterminateSystems/flake-checker/0.1"This command reveals that the flake has two output types, devShells and packages.
Not too exciting, as this has been possible in Nix with flakes for quite some time.
Let’s get a bit more spicy and see what happens when we inspect a flake with non-standard outputs:
nix flake show \ --all-systems \ "github:DeterminateSystems/nixos-amis"You’ll see some standard outputs here, like apps and devShells, but you’ll also see this:
├───diskImages│ ├───aarch64-linux│ │ └───aws: Disk image│ └───x86_64-linux│ └───aws: Disk image├───epoch: Determinate NixOS AMIs epoch 1These are not standard outputs!
And yet Determinate Nix is displaying information about them.
That’s because this DeterminateSystems/nixos-amis flake has a schemas output that includes the default schemas but also adds two custom ones:
{ diskImages = { version = 1; doc = '' The `diskImages` flake output contains derivations that build disk images for various execution environments. ''; inventory = inputs.flake-schemas.lib.derivationsInventory "Disk image" false; };
epoch = { version = 1; doc = "The `epoch` output provides a simple string value that's meant to be updated whenever there are breaking changes to the AMIs."; inventory = output: { what = "Determinate NixOS AMIs epoch ${output}"; shortDescription = "A string representing the epoch value: ${output}"; evalChecks.isString = builtins.isString output; }; };}In this flake, diskImages are NixOS disk images that can be used as AWS AMIs, while epoch is a static string that’s updated any time there are breaking changes.
Previously, these outputs would have showed up as unknown when running nix flake show, but now any passerby can know what they are in plain English (or another language, if you prefer!).
The programming model
Flake schemas are driven by a pretty straightforward programming model.
At root, Determinate Nix no longer has any hard-coded schemas.
Instead, it uses the schemas in the DeterminateSystems/flake-schemas repo by default.
Those schemas will slowly evolve over time and are intended to provide a nice out-of-the-box experience for Determinate Nix users.
If your flake conforms to those schemas—with outputs like packages, devShells, apps, nixosConfigurations, and others—then you’re already set.
But we know that out-of-the-box experiences aren’t for everyone and that many organizations will have custom requirements. If you have
For any existing flakes, you can add the DeterminateSystems/flake-schemasschemas output:
{ inputs = { # other inputs flake-schemas.url = "https://flakehub.com/f/DeterminateSystems/flake-schemas/*"; };
outputs = { self, ... }@inputs: { # other outputs
schemas = inputs.flake-schemas.schemas // { # custom schemas here }; };}The flake-schemas flake also provides some helper functions for things like recursing through nested output structures.
If you want to declare non-default schemas in a new flake, we have a flake template to make that a bit easier:
nix flake init \ --template "https://flakehub.com/f/DeterminateSystems/flake-templates/0.1#flake-schemas"In some cases, you may want to change the underlying default schemas for a single nix flake show invocation.
There’s a --default-flake-schemas option for that:
nix flake show \ --default-flake-schemas "https://flakehub.com/f/my-org/my-schemas/0.1" \ "github:DeterminateSystems/nixos-amis"This can be useful for experimenting with custom schemas or providing an override in specific contexts.
How we’re using flake schemas internally
Here at Determinate Systems, we have a handful of projects that use special output types. One such project is something we call Minnows, which is a new framework for building lean, secure, production-ready Linux systems, particularly targeted at embedded systems on non-standard hardware.
Rather than building on NixOS, which has some major drawbacks for our this use case, we opted to develop a new set of constructs.
Instead of modules, your Minnows system’s workloads are systemd units called flows and you can add support for hardware targets by creating your own platform definitions.
Neither of these constructs maps onto any existing output types, and so we wrote up two schemas, minnowsFlows and minnowsPlatforms respectively, to cover them.
Now we can use nix flake show to discover which flows or platform definitions are exposed by a flake, which is a major ergonomic win for us—and the kind of ergonomic win we envision for other Nix-powered organizations.
This project is currently in private beta but we’ll be saying much more about it soon. If you’re in the embedded Linux space or have found NixOS somehow lacking when building production systems, you’ll definitely want to stay tuned.
Changing the default schemas
As I mentioned, Determinate Nix now uses the schemas in the DeterminateSystems/flake-schemas repo as the built-in defaults, with each future Determinate Nix release incorporating the most recent release of that flake.
But these schemas are not set in stone and we expect them to change over time as Nix covers an expanding range of use cases and reaches into ever-new sectors of the software world.
We at Determinate Systems are open to issues and pull requests, though please be aware that we intend to add and change schemas quite conservatively. If you’d like us to add or change a schema, be prepared to make an evidence-backed case for it that points to established usage patterns.
Coming soon: configurable flakes
We’re confident that flake schemas will be a major step forward for discoverability in Nix flakes. But we have plans that extend beyond this. Flake outputs are currently static because you can’t (yet) provide them with arguments. So if you want to build, for example, subtle variations on a package, you need to produce explicit flake outputs for each of those packages.
But this is not ideal in cases where users of your flake may want to provide their own input. To make this possible, we’re currently at work on configurable flakes, which will enable you to specify type-safe inputs to flake outputs. Check out Eelco Dolstra’s talk at SCALE 2025, titled Configurable flakes, for an in-depth picture.
Please note that just about everything involving configurable flakes is subject to change. Read this section of the post only as a sneak peek at the functionality we want to provide.
For now, let’s take a brief look at what a configurable flake would look like. Here’s an example Nix package that takes an optional value:
{ outputs = { self, ... }@inputs: { packages = forEachSupportedSystem ( { pkgs }: { hello = { options = { who = { type = lib.types.str; default = "World"; }; };
applyOptions = { who }: pkgs.stdenv.mkDerivation { name = "hello"; dontUnpack = true; installPhase = '' mkdir -p $out/share echo "Hello, ${who}!" >> $out/share/hello.txt ''; }; }; } ); };}In this package output, options specifies that who is a string argument with a default of "World", while the applyOptions function specifies how the value of who is used in the derivation.
Now, anyone can supply their own value for who using a --who option:
nix build "https://flakehub.com/f/my-org/my-pkgs/0#hello" \ --who "Eelco"You could then inspect the built file…
cat ./result/share/hello.txt…and see "Hello, Eelco!" as a result.
Cool, but what about introspectability? How can I see which options are available? We’re envisioning something like this:
nix describe "https://flakehub.com/f/my-org/my-pkgs/0"This would act a bit like nix flake show but would instead reveal all of the flake outputs that take configurable options and describe the data types and defaults of those options.
Flakes are becoming programmable
With Determinate Nix, we are cracking flakes open and making them more flexible and extensible in some key areas. Flake schemas are already doing this, today, and we recommend that you take stock of all of your current flakes to ensure that they’re as introspectable as they could be. If you have non-standard output types, now is a great time to enable your flakes’ users to know more about them.
Configurable flakes will be the evolutionary next step in making flakes programmable, enabling you to not just un-hard-code outputs but rather fundamentally rethink the entire purpose of flakes. After all, if flakes take options, then they themselves become type-safe functions in a brand new way.
How to get Determinate Nix
If you already have Determinate Nix installed, you can upgrade to 3.17.0 with one Determinate Nixd command:
sudo determinate-nixd upgradeIf you don’t yet have Determinate Nix installed, you can upgrade or migrate to Determinate Nix on macOS using our graphical installer:
Install Determinate Nix on macOS now 🍎
With support for Apple Silicon (aarch64-darwin)
On Linux:
curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | \ sh -s -- install --determinateOn NixOS, we recommend using our dedicated NixOS module or our NixOS ISO (NixOS installer for x86_64, NixOS installer for ARM) with Determinate Nix pre-installed.
On GitHub Actions:
on: pull_request: workflow_dispatch: push: branches: - main
jobs: nix-ci: runs-on: ubuntu-latest # Include this block to log in to FlakeHub and access private flakes permissions: id-token: write contents: read steps: - uses: actions/checkout@v5 - uses: DeterminateSystems/flake-checker-action@main - uses: DeterminateSystems/determinate-nix-action@v3 - uses: DeterminateSystems/flakehub-cache-action@main - run: nix flake checkIn Amazon Web Services:
data "aws_ami" "detsys_nixos" { most_recent = true owners = ["535002876703"]
filter { name = "name" values = ["determinate/nixos/epoch-1/*"] }
filter { name = "architecture" values = ["x86_64"] }}