Skip to content
Open
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
311 changes: 311 additions & 0 deletions rfcs/0193-toml-flakes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,311 @@
---
feature: toml-flakes
start-date: 2025-12-07
author: Robert Hensing (@roberth)
co-authors: (find a buddy later to help out with the RFC)
shepherd-team: (names, to be nominated and accepted by RFC steering committee)
shepherd-leader: (name to be appointed by RFC steering committee)
related-issues: (will contain links to implementation PRs)
---

# Summary
[summary]: #summary

Make flakes easier to use, automate and learn.

Introduce `flake.toml` as the new leading file for flakes,
separating input specifications from output definitions.
The `flake.toml` file contains declarative metadata and input declarations,
while `flake.nix` (read by the default entrypoint) or a framework focuses on output definitions.

Fixes [#4945 What language is flake.nix written in?](https://github.com/NixOS/nix/issues/4945)

# Motivation
[motivation]: #motivation

Currently, flakes combine two distinct concerns in a single `flake.nix` file:
declaring dependencies (inputs) and implementing functionality (outputs).
This creates several problems:

1. **User uncertainty**: The current structure creates confusion about what language features are available
and when evaluation restrictions apply,
because inputs and outputs are mixed in the same file, and inputs are not allowed to be arbitrary Nix expressions.
Copy link

@ruro ruro Dec 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like this RFC should not just assume that the currently enforced status-quo limitation of "Flake input metadata can't contain arbitrarily complex, possibly non-terminating computations" must be kept as is, without justification.

I'm not necessarily saying that we should remove this restriction, but the RFC should definitely explore the pros / cons of this requirement more.

For example, do we know if this restriction is actually useful / needed? I think, I read somewhere that FlakeHub is relying on the fact that flake metadata can be computed statically-ish (I've never used FlakeHub, so I am not sure if this is actually the case). Are there any more examples where this property is actually useful?

If FlakeHub is the only (or one of the few) users of this restriction / guarantee, then maybe it should be implemented as a simple evaluation "depth" limiter on the side of FlakeHub, instead of in Flakes themselves?

Or maybe it's enough to have a simple way to opt-out of this restriction? Something like

{
    dynamic = true;
    inputs = {
        # Arbitrary nix expressions are now allowed inside inputs,
        # because `dynamic` was set to true. This flake might not have
        # full functionality on services like FlakeHub, but this is fine.
    };
}

See [issue #4945](https://github.com/NixOS/nix/issues/4945).
Note that while the question was originally asked in a partly rhetorical manner,
it is still a valid question,
a variation of which pops into new users' minds.
Some learning is always required,
but this is an unnecessary bump in the curve.

2. **Limited automation**: Programmatically editing flake inputs requires Nix AST manipulation,
which is complex and error-prone compared to editing structured data formats.

3. **Boilerplate**: Common flake patterns require repetitive Nix code
that could be handled by frameworks if inputs were separated from implementation.

By moving input specifications to a simpler format,
we enable better tooling,
reduce user confusion about evaluation restrictions,
and create a clearer separation of concerns between dependency declarations and output implementations.

# Detailed design
[design]: #detailed-design

## File structure

`flake.toml` becomes the leading file,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's one of the greatest features of the nix ecosystem that everything is configured in a single language.

I doubt that adding more configuration languages makes nix more approachable, since people will still have to understand both languages.

Copy link
Contributor

@iFreilicht iFreilicht Dec 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do love that as well, but there is a big difference between "nix the turing-complete functional language" and "nix the language for flake inputs, flake config and flake metadata that only allows attrsets and literals".

Maybe there could be a separate file format for a subset of nix called "nix object notation" or "nix light", a language that has nix syntax but doesn't allow functions at all?

This is pretty similar in concept to zig's .zon format, which reuses zig's syntax for objects to create a json-like format.

It would just need a different file ending (though I'm not sure .nixon is a great idea :D). You could import files like that just like regular nix files, but for certain things (like the flake metadata in this RFC), only this subset of nix is allowed.

EDIT: Just noticed someone else brought that up already: https://github.com/NixOS/rfcs/pull/193/files#r2602798399

containing input sources, follows relationships, and entrypoint selection.
It complies with the [Nix JSON guideline](https://nix.dev/manual/nix/latest/development/json-guideline.html) (modulo `null`).

Flake output invocation is changed to go through an "entrypoint", which is the flake that provides a function to help produce the flake output attributes based on the source tree of `flake.toml`.
If `flake.toml` has an `entrypoint` field, it must name one of the `inputs` which will serve as the entrypoint.
Otherwise, if an input named `"entrypoint"` exists, it becomes the entrypoint.
Copy link

@TLATER TLATER Dec 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can imagine use cases for declaring multiple entrypoints that define different sets of outputs.

If multiple are defined, nix could basically just recursively // the outputs of the function returns; collisions should be uncommon if the ecosystem evolves around their possibility, and most use cases for this would probably be to provide two different outputs which would be completely orthogonal anyway.

E.g., having the option of specifying multiple entrypoints would make writing a flake that has both nixosConfigurations and homeManagerConfigurations possible, without the boilerplate of maintaining a specialized entrypoint flake for this use case.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A simple flake could do that too. An entrypoint flake is just a flake with a specific function in it; anyone can call them. So you could have a flake that performs this merging task.

I'd argue that flake-parts already performs this task well, providing a decent framework for interaction between modules, as opposed to entrypoints which would typically assume ownership of the outputs, and collide at the various default attributes.

Copy link

@ruro ruro Dec 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A simple flake could do that too. ..., as opposed to entrypoints which would typically assume ownership of the outputs, ....

Actually, this begs the question if the special inputs.entrypoint magic should even be part of this proposal. It seems to me that everything that inputs.entrypoint does can already be performed by essentially doing outputs = inputs: inputs.entrypoint.makeFlake inputs ./. ...;.

Based on the title of this RFC, it feels like inputs.entrypoint might be out of scope? Maybe extra convenience features that extend the current functionality of flakes like inputs.entrypoint should be considered in separate RFCs (possibly together with stuff like NixOS/nix#5663, NixOS/nix#3843, NixOS/nix#7422 (comment), NixOS/nix#5157, NixOS/nix#5567)?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Scope could be reduced in that way. I like the bigger picture of the combined changes, but for clarity that seems like a good idea actually.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Finally if `flake.nix` exists, the entrypoint is the default entrypoint.

`flake.nix` is read by the default entrypoint and defines outputs.

## Choice of TOML

See also the [alternatives] section.

A nice aspect of TOML is that its non-nested syntax aligns with part of a definition of _declarative systems_, having a set of _independent statements_.

It has a wide ecosystem of libraries for parsing, as well as good number of libraries that support round tripping edits.

## Relationship to existing files

- `flake.lock` remains unchanged
Comment on lines +67 to +69
Copy link
Member

@GetPsyched GetPsyched Dec 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since flake.toml defines just the inputs and the lockfile is also just metadata about inputs being pinned, could we merge flake.lock with flake.toml? Is this too radical of a change? I personally would like to not maintain 3 files for flakes;

  • one for inputs flake.toml,
  • one for outputs flake.nix/outputs.nix, and
  • one for pinning the inputs flake.lock.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also think that using three separate files is a bit excessive for flakes. And since flakes are still experimental, supporting users who prefer non-experimental workflows means you’ll need to provide a shell.nix if your development setup relies on flakes, as well as a default.nix if you export packages so they remain compatible with import clauses.

Regarding the idea of merging flake.toml and flake.lock, I don’t believe that would be advisable. For example, template repositories (such as those in NixOS/templates) depend on listing flake.lock in their .gitignore file to ensure it’s not committed to git. If flake.toml and flake.lock were combined, git wouldn’t offer a way to ignore only the lock changes made to that file. As a result, updating a template would require manual review to make sure no lock information is inadvertently added to flake.toml.

This is especially important because Nix automatically generates flake.lock (and, in turn, would update flake.toml once that file is introduced). That behavior could easily lead to unintended changes being committed without the developer noticing.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct me if I'm wrong, but aren't flake.lock files usually automatically generated and left untouched by the user until an upgrade command is run?

Aside from tooling simplicity and reliability, for the use case of creating templates, wouldn't it make omitting the usually unnecessary version locks much more convenient for template creators?
As of now, you can keep version locks out of your repository with a simple .gitignore entry and keep iterating on the template directly with no additional work.
With the merge of flake.toml and flake.lock files, the process would not be as simple, requiring things like omitting generated changes from commits, using special command arguments, or just biting the bullet.

I'm not saying that it'd be a big burden with either case, but keeping the files separate just seems like the cleaner and easier to manage solution.

Copy link

@username-generic username-generic Dec 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@limwa Oh sorry, I didn't see your comment before starting to write mine! But I guess I'm not alone on this, since we wrote up the exact same example.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

flake.lock would no longer be a throw-away file, and the editing experience would get a bit worse.
We'd have to ensure that the most relevant bits stay at the top.
JSON doesn't support comments (or automation support may regress if you do allow it)
GitHub hides lockfile changes, and even if that's changed, you'd have to open very large diffs even if you trust the committer.

I lean towards preserving full flake.nix support without warnings, so that might be a good solution for you.

Also, fwiw, if you're using a framework, then combined with #194, you may not need a flake.nix or outputs.nix anymore.

- Alternative entrypoints (referenced in `flake.toml`) can read different files
- `flake.nix` remains supported as the default entrypoint, and as a legacy format for the `inputs` and other metadata.

## Compatibility

The design should support reading legacy `flake.nix` files that contain inline input specifications.

The following negative space is changed and may require a few projects to adapt if they already use these:
- A flake input named `entrypoint` is assigned meaning, changing how flake outputs are formed.
- A file named `flake.toml` will shadow `flake.nix` in file discovery

Flakes that do not have any of the above elements remain compatible.

A TOML flake can not be evaluated by older implementations of Nix.

Other than those constraints, TOML and traditional flakes can be used and migrated back and forth without compatibility problems, as their usages in the CLI or as an `inputs` dependency do not change.

Validation and adoption can start in flakes with fewer users, such as those without reverse dependencies.

# Examples and Interactions
[examples-and-interactions]: #examples-and-interactions

## Current flake.nix

```nix
{
description = "A simple example flake exporting GNU hello for x86_64-linux";

inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
};

outputs = { self, nixpkgs }:
let
system = "x86_64-linux";
pkgs = nixpkgs.legacyPackages.${system};
in {
packages.${system}.default = pkgs.hello;
};
}
```

## Proposed structure, simple example

**flake.toml**:
```toml
description = "A simple example flake exporting GNU hello for x86_64-linux"

[inputs.nixpkgs]
url = "github:NixOS/nixpkgs/nixos-unstable"
```

**flake.nix** (read by default entrypoint):
```nix
{
outputs = { self, nixpkgs }:
let
system = "x86_64-linux";
pkgs = nixpkgs.legacyPackages.${system};
in {
packages.${system}.default = pkgs.hello;
};
}
```

**flake.lock** remains unchanged.

## Proposed structure, framework entrypoint

**flake.toml**:
```toml
description = "A simple example flake exporting GNU hello for x86_64-linux"

[inputs.nixpkgs]
url = "github:NixOS/nixpkgs/nixos-unstable"

[inputs.entrypoint]
url = "github:hercules-ci/flake-parts"

[inputs.entrypoint.inputs.nixpkgs-lib]
follows = "nixpkgs"
```

**parts.nix** (read by default entrypoint):
```nix
{ ... }: {
perSystem = { pkgs }: {
packages.default = pkgs.hello;
};
}
```

**flake.lock** remains unchanged.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess you implicitly add locking the entrypoint flake?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is a regular input, so it is locked like any other input.
In an earlier iteration of this RFC I had it as a separate lock entry entrypoint.url that wasn't part of inputs, but that would just complicate the locking mechanism.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh right, I see that in some place you say that entrypoint is used if specified but elsewhere it is inputs.entrypoint. (Maybe it is better to make all mentions fully qualified?)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The possibility of specifying a different entrypoint than "entrypoint" in flake.toml's entrypoint field would make that inaccurate. Clearly this needs further simplification. Let me strip that indirection out.


## Entrypoint function

`flake.nix` serves as the base that bootstraps entrypoints.
This example shows what authoring a framework looks like.
An entrypoint is invoked as `<flake outputs>.lib.callFlakeEntrypoint`:

```nix
{
outputs = { self, nixpkgs }:
let
system = "x86_64-linux";
pkgs = nixpkgs.legacyPackages.${system};
in {
packages.${system}.default = pkgs.hello;

# Entrypoints are accessed here
lib.callFlakeEntrypoint =
{
# The resolved and invoked/dependency-injected flake inputs
inputs,
# The parsed flake.toml
flakeToml,
# The invoked/dependency-injected *result*
self,
# The base directory of flake.toml
flakeDir,
# The sourceInfo, where `sourceInfo.outPath` may be `flakeDir` or any
# parent of it (if applicable; subdirectory flakes)
sourceInfo,
# ... is mandatory for forward compatibility
...
}:
# Imagine some useful expression, returning the usual flake outputs
{
packages = { ... };
apps = { ... };
};
};
}
```

The default entrypoint reads `flake.nix` and expects an `outputs` attribute.
Alternative entrypoints (specified in `flake.toml`) can implement different conventions.

The exact schema shown above is illustrative.
The final design will follow the Nix JSON guideline for extensibility,
using records at appropriate levels and `null` for optional values.

# Drawbacks
[drawbacks]: #drawbacks

- Introduces another file format into the Nix ecosystem
- Migration cost for existing flakes
- Potential confusion during transition period with two ways to specify inputs
- Requires tooling updates across the ecosystem

# Alternatives
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One alternative worth considering: use Dhall instead of TOML.

Many of the complaints about the flake.nix ‘language’ seem to be rooted in the desire to abstract out parts of the inputs for reuse. TOML doesn't actually address this desire; it merely makes it clearer to the user that the desire can't be satisfied. The motivation to restrict how inputs are expressed seems to be to prevent arbitrary code from running, either for security reasons or so that flake input analysis can be guaranteed to terminate. Dhall addresses both concerns, while allowing many of the abstractions flake users are trying to write; it's pretty much made for exactly this use case.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That direction is more in line with the discussion that followed in NixOS/nix#4945.
Dhall is still somewhat impractical, unless perhaps the Rust implementation could be wrapped such that Nix can use it as a library.
I believe its effect on the learning curve is also negative.
The main use case of programmatic inputs seem to be auto-follows, which could just be a feature, or getting inputs from some shared expression.
I personally wouldn't mind allowing evaluation in inputs. If someone wants to make them complicated, non-terminating, whatever, that's on them.

Regarding Dhall as a point in the configuration language design space, I might upset some folks, including my past self who was a big fan of that kind of tech before using it in conjunction with Nix.
While I appreciate the science of it, I find Turing incompleteness to be a bit of a gimmick and an unnecessary constraint for this kind of application. It has a real cost in terms of what kinds of programs can be expressed with it, while gaining very little practical value: either you get an error, or you don't.
The tradeoff is between getting 1 success and 1 mediocre error from a language like Nix vs 1 unnecessary impossibility and 1 nicer error from Dhall.
Or we could just keep it boring and use TOML.

I guess we could add a helper trampoline primop to Dhall to add some Turing completeness for the algorithmically inclined?

I'll boil this rant down into a more reasonable addition later, or I'd be happy to take a GitHub suggestion.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yaml could also take care of a lot of the desires here, without being fully turing complete, and without being as obscure. It has a variety of variable-like features.

That said, I think simplifying the format as much as possible, and indicating that it is deliberately limited is part of the appeal of this proposal. Making inputs templateable just leads to complexity that ends up confusing newcomers to the ecosystem.

I think most use cases for evaluation in inputs that don't just become features outright could be handled in external scripts instead, given the file would be much more programmatically editable.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not myself convinced that Dhall would be the best choice, but a lot of these objections strike me as not very strong.

Dhall is still somewhat impractical, unless perhaps the Rust implementation could be wrapped such that Nix can use it as a library.

Even if there is no convenient binding, Nix could execute dhall-to-json --file flake.dhall and use the output; I'd be shocked if the overhead of a child process when interpreting flake input files would have any human-noticeable impact on perf.

I believe its effect on the learning curve is also negative.

Simple flake.dhall file:

{
  nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable",
  entrypoint = {
    url = "github:hercules-ci/flake-parts",
    inputs.nixpkgs-lib.follows = "nixpkgs",
  },
}

I don't think this would present more of a learning challenge to people not trying to dive into the intricacies of Dhall than the equivalent TOML would.

In other words: the strength of the argument that people don't need abstractions in their inputs is inversely proportional to the strength of the argument that it would be difficult to read and write inputs written in Dhall. If many people want it, all the more reason to use a non-data-only language. If few people want it, we won't see ‘advanced’ Dhall features (scare quotes because, you know, I'm talking about things like let bindings) very often in the wild (and users can make up their own mind whether having them is worth learning about them).

And it's worth emphasizing that this is not a hypothetical ‘what if people want it’. We know users want it. Here's this example as it might have been written in Dhall:

let dep = \(url : Text) -> { url = url, inputs.nixpkgs.follows = "nixpkgs" }
in {
  nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable",
  entrypoint = {
    url = "github:hercules-ci/flake-parts",
    inputs.nixpkgs-lib.follows = "nixpkgs",
  },
  nix-darwin = dep "github:LnL7/nix-darwin",
  home-manager = dep "github:nix-community/home-manager",
  zig = dep "github:Cloudef/nix-zig-stdenv",
  zls = dep "github:zigtools/zls",
  hyprland = dep "github:hyprwm/Hyprland",
  eww = dep "github:elkowar/eww",
}

This strikes me as something that said user would have been able to learn how to write, given what they wrote in Nix.

It has a real cost in terms of what kinds of programs can be expressed with it

When compared to TOML as the alternative, this isn't any sort of disadvantage; the programs TOML is able to express are a strict subset of the programs Dhall is able to express, obviously.

The tradeoff is between getting 1 success and 1 mediocre error from a language like Nix vs 1 unnecessary impossibility and 1 nicer error from Dhall.
Or we could just keep it boring and use TOML.

Using (unrestricted) Nix would be keeping it boring. In the same framework, the tradeoff is between TOML's 2 unnecessary impossibilities (not-provably-total programs, and simple deduplication of the sort we actually see people trying to write) and 1 nicer error, Dhall's 1 unnecessary impossibility (NPT programs) and 1 nicer error, or Nix's 0 unnecessary impossibilities and 1 mediocre error. On just these axes, TOML is not on the efficient frontier.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nix could execute dhall-to-json --file flake.dhall

Performance is probably acceptable, but the static Nix binary would have to pull that in from somewhere for basic functionality.

simplifying the format as much as possible, and indicating that it is deliberately limited is part of the appeal of this proposal.

This. It brings clarity and better scripting.

The comparison in this thread was between Dhall and full-Nix, but the RFC does not propose either.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TOML has been chosen by projects like Rust and Python for the exact reason that it is necessarily simple. It doesn't let you get too fancy. YAML was ruled out because it allows too much flexibility. YAML is also bad at making things clear as complexity increases. Anyone complaining about Nix as an obscure language will not find Dhall an improvement. Anyone who needs the complexity should use Nix. Anyone who doesn't need it can use TOML.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@de11n toml is not that simple though, especially with the table vs array syntax - e.g. this is quite atrocious: https://toml.io/en/v1.0.0#array-of-tables

Let's just stick to Nix.

[alternatives]: #alternatives
Copy link
Member

@matklad matklad Dec 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One more alternative is to explicitly define "data only" subset of nix which guarantees O(input length) eval times, and introduce special name&extension&file for it.

C.f build.zig and build.zig.zon (Zig object notation) in Zig, or, for that matter, index.js and package.json.

We could have flake.nix and flake.nix.nox (Nix Object eXpression).

This of course presupposes that we do not want to "program" flake.nox.

Given that nix can be on the bootstrap path, it seems relatively valuable to stick to what we already have, rather than introduce extra dependencies on other formats.

EDIT: we could also think about using nox instead of aterms in derivations?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it's because I have a simple mind, but I have great difficulty with nox if we could call it Nixon.

Anyway, on a more serious note, but still indulging myself, those would be highly similar formats.
I'd say step 1: allow Nix identifiers as object keys, trailing comma, remove decimal numbers => Nixon.
Step 2: rebase the Nix language on top of that, removing floats and with, maybe some other things => Jix

Material for some follow-up RFCs ;)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if that's a follow-up RFC or a prerequisite. A simple format like this could be quite useful for many situations. flake.lock could be transitioned to a format like this, the profile manifest as well. It seems like a pretty useful idea to formalize this "data only subset" of nix.

It would also alleviate the concerns raised here: https://github.com/NixOS/rfcs/pull/193/files#r2603388837


## Keep current structure

Maintain the status quo including drawbacks described in the [motivation].

## Pure Nix with better tooling

Improve Nix AST manipulation tools instead of introducing a new format.
Does not solve user uncertainty.
Does not solve boilerplate.
Copy link

@crertel crertel Dec 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll point out that the main complaints about this somewhat go away with taking "pure nix" tooling and also taking the admission that both user uncertainty and boilerplate in 2025 are concerns that are rapidly declining.

Bluntly: any of the major LLMs right now are perfectly fine at making decent flake.nix files, and where they fall short a different configuration language or helper probably wouldn't be much of an improvement.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was in reference to the outputs function more so than the inputs.

Boilerplate wasn't much of a problem even before LLMs.
Realistically how does reading 20-40% extra code which is visually similar stack up against having to evaluate code before being able to see the outcome?
Explicit is good.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a fair point.


## Use JSONC

JSONC is somewhat more aligned with the Nix language, including the presence of `null`,
and the approximate syntax for objects/attrsets, while still allowing comments.

## Use YAML

YAML is widely supported but is despised among many for such things as [the Norway Problem](https://hitchdev.com/strictyaml/why/implicit-typing-removed/).

It is hard to parse correctly,
which poses a significant risk for an ecosystem with a major goal of supporting reproducibility,
as even an innocuous whitespace fix in parser output can cause a rift
where binary caches aren't shared between Nix versions and old expressions produce new outcomes.

# Prior art
[prior-art]: #prior-art

## Experimental `configs` commit by Eelco

[1dc3f53](https://github.com/NixOS/nix/commit/1dc3f5355a3786cab37a4de98ca46a859e015d89), part of stable branch [`configs`](https://github.com/NixOS/nix/compare/configs) implemented a `flake.toml` file.
It incorporated a "poor man's module system" into Nix where it would be of very limited functionality,
while it ossifies due the consequences in Nix of [Hyrum's law](https://www.hyrumslaw.com/) and the second order effects of reproducibility.

## devenv

The devenv tool uses a separate configuration format for specifying dependencies
and generating Nix configurations,
demonstrating that declarative input specifications can work well in practice.

They have also integrated input declarations and input usage in their application of the module system.
This RFC could be extended or followed up with a metadata feature that generalizes this idea and allows an entrypoint to achieve a similar effect.

```toml
[inputs.entrypoint]
url = "github:cachix/git-hooks.nix"
# forwarded as e.g. `inputUsages.<input>` to entrypoint, so it can import/enable/etc as intended.
usage = [ "flake-parts" ]
```

## flake-parts, flake-utils-plus and blueprint

Framework systems like flake-parts, flake-utils-plus and blueprint already provide abstractions over flake outputs.
This proposal would enable these frameworks to be specified declaratively,
in a standard way,
without boilerplate.

## Other language ecosystems

Most package managers separate dependency declarations from implementation code (`Cargo.toml`, `requirements.txt`, and to a fair degree `package.json`).
This proposal brings Nix flakes closer to that common pattern.

# Unresolved questions
[unresolved]: #unresolved-questions

- What outcomes result from a prototype of this feature?
- Schema validation for the inputs file
- How follows relationships are expressed in TOML

# Future work
[future]: #future-work

- Evaluate TOML round-tripping libraries

# Credit

Having been part of the Nix team,
I suspect that I've learned some of these ideas from the team, especially Eelco,
who has experimented in this direction.

Also another thank you to username-generic for asking the question in a recent [discourse thread](https://discourse.nixos.org/t/outlining-the-differences-between-flakes-and-nix-configs/72996/7),
and together with TLATER for making me realise I should just go ahead and write this RFC.