Angle brackets in a Nix flake world
Published 2025-08-10 on Farid Zakaria's Blog
At DEFCON33, the Nix community had its first-ever presence via nix.vegas and I ended up in fun conversation with tomberek ๐.
โWhat fun things can we do with <
and >
with the eventual deprecation of NIX_PATH
?
The actual ๐ก was from tomberek and this is a demonstration of what that might look like without necessitating any changes to CppNix itself.
As a very worthwhile aside, the first time presence of the Nix community at DEFCON was fantastic and I am extra appreciative to numinit and RossComputerguy ๐. The badges handed out were so cool. They have strobing LEDs but also can act as a substituter for the Nix infra that was setup.
Okay, back to the idea ๐.
Importing nixpks via the NIX_PATH
through the angle-bracket syntax has been a long-standing wart on the reproducibility promises of Nix.
let pkgs = import <nixpkgs> {};
in
pkgs.hello
There is a really great article about all the problems with this approach to bringing in projects on nix.dev, for those whom are still leveraging it.
With the eventual planned removal of support for NIX_PATH
, we are now presented with an opportunity of some new functionality in Nix, namely the angled brackets <something>
that can be reconstituted for a new purpose.
Looks like others are already starting to think about this idea. The project htmnix demonstrates the functionality of writing pure-HTML but evaluating it with
nix eval
๐.
For something potentially more immediately useful, how about giving quicker access to the attributes of the current flake? ๐ค
A common pattern that has emerged is to inject inputs
and outputs
into extraSpecialArgs
so that they are available to modules in NixOS or home-manager.
{
homeConfigurations = {
"alice" = home-manager.lib.homeManagerConfiguration {
pkgs = nixpkgs.legacyPackages."aarch64-darwin";
extraSpecialArgs = {inherit inputs outputs;};
modules = [
./users/alice
];
};
}
This lets you add the modules from your inputs
or reference the packages in your outputs
from within the modules themselves.
{
inputs,
pkgs,
lib,
config,
osConfig,
...
}: {
imports = [
./git.nix
inputs.h.homeModules.default
inputs.nix-index-database.homeModules.nix-index
];
That seems nice but also unnecessary. Why not leverage the angled brackets for the same purpose. โ๏ธ
That would make the equivalent example without needing to now wire up the inputs
.
{
pkgs,
lib,
config,
osConfig,
...
}: {
imports = [
./git.nix
<inputs.h.homeModules.default>
<inputs.nix-index-database.homeModules.nix-index>
];
Is this possible today? Yes!
Whenever Nix sees angled brackets <something>
it desugars the expression to a call to __findFile
.
> nix-instantiate --parse --expr "<hello>"
(__findFile __nixPath "hello")
If we offer a variant of __findFile
in scope, Nix will call our implementation rather than the default implementation.
Letโs implement a variant that utilizes builtins.getFlake
to return the current flake attributes.
Our goal is to write something as simple as the following and have the contents within the angle brackets be treated as an attribute path of the flake.
<outputs.hello> + " and welcome to Nix!"
What do we have to do to get this to work?
Well we need to provide our own version of __findFile
.
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
nixpkgslib.url = "github:nix-community/nixpkgs.lib";
};
description = "Trivial flake that returns a string for eval";
outputs = {
nixpkgslib,
nixpkgs,
self,
}: {
__findFile = nixPath: name: let
lib = nixpkgslib.lib;
flakeAttrs = builtins.getFlake (toString ./.);
in
lib.getAttrFromPath (lib.splitString "." name) flakeAttrs;
hello = "Hello from a flake!";
example = builtins.scopedImport self ./default.nix;
};
}
We write a function of __findFile
that trivially splits the contents within the angle bracket to access the attrset of the flake as returned by builtins.getFlake (toString ./.)
.
There is some additional magic with
builtins.scopedImport
๐ช which is not documented. It allows giving a different base set of variables, via a provided attrset, to use for variables. This is how we can override__findFile
in all subsequent files.
So does this even work?
> nix eval .#example --impure
"Hello from a flake! and welcome to Nix!"
Yes! ๐ฅ With the caveat that we had to provide --impure
since getting the current flake via ./.
requires it.
This is a pretty ergonomic way to access the attributes of the current Flake automatically without having us all to go through the same setup for what is amounting to common best practices.
The need to have --impure
is a bit of a bummer although this is a pretty neat improvement. There could be a new builtin, builtins.getCurrentFlake
, which automatically provides the context of the current flake and therefore could be pure.
Improve this page @ 775be58
The content for this site is
CC-BY-SA.