Ergonomic overrides for Nixpkgs

Lobsters Hottest Tools

Summary

Gabriella439 introduces override-utils, a new package that simplifies Nixpkgs overrides and overlays, aiming to improve the usability of the Nix ecosystem.

<p><a href="https://lobste.rs/s/yfj5qc/ergonomic_overrides_for_nixpkgs">Comments</a></p>
Original Article
View Cached Full Text

Cached at: 06/08/26, 03:19 AM

# Ergonomic overrides for Nixpkgs Source: [https://haskellforall.com/2026/06/ergonomic-overrides-for-nixpkgs](https://haskellforall.com/2026/06/ergonomic-overrides-for-nixpkgs) I created a new[`override\-utils`package](https://github.com/Gabriella439/override-utils)for simplifying Nixpkgs overrides/overlays, which is part of a broader project of mine to improve the usability of Nixpkgs\. This post will focus more on the big picture but I'll also motivate the`override\-utils`package\. ## Background Several years ago I wrote["The hard part of type\-checking Nix"](https://haskellforall.com/2022/03/the-hard-part-of-type-checking-nix), which was my take on the usability issues plaguing the Nix ecosystem\. The relevant excerpt from that post is: > The fundamental problem that plagues all type\-checking attempts for Nix is that nobody actually uses**Nix the language**at any significant scale\. Instead, the community has adopted two sub\-languages embedded within Nix for programming “in the large”: - Nixpkgs overlays This is an embedded language that simulates object\-oriented programming with inheritance / late binding / dynamic scope \(depending on how you think about it\) - NixOS modules This is an embedded language that roughly emulates[Terraform](https://github.com/hashicorp/terraform) Carefully note that these are not language features built into Nix; rather they are embedded domain\-specific languages implemented within Nix\. Consequently, a type\-checker for “Nix the language” is not necessarily equipped to type\-check these two sub\-languages\. In other words, the problem with Nix isn't that Nix doesn't have types; it's that even if Nix**had**types they'd be just as impenetrable as current stack traces because Nix is operating at the wrong level of abstraction\. We need to design better abstractions before we can build a type system that works well in inexpert hands\. ## Design To this end, I sat down and asked myself: "if I could build a programming language purpose\-built for working with Nixpkgs, what would that ideal language look like?"\. I figured this would be a useful thought experiment, but also I have enough experience with implementing programming languages that I could perhaps build out a proof of concept if I thought it were compelling enough\. While designing this "Nixpkgs language" I realized that I struggle most with override functions and overlays so I started there\. To illustrate what I mean, suppose that I needed to add the`libvirt`package as a native dependency to Haskell's`libvirt\-hs`package[1](https://haskellforall.com/2026/06/ergonomic-overrides-for-nixpkgs#user-content-fn-1)\. I'd have to do something like this: ``` final: prev: { haskellPackages = prev.haskellPackages (old: { overrides = hfinal: hprev: { libvirt-hs = final.haskell.lib.overrideCabal hprev.libvirt-hs (old: { libraryPkgconfigDepends = (old.libraryPkgconfigDepends or []) ++ [ final.libvirt ]; }); }; }); } ``` Gross\. Moreover, it's an even bigger pain if I want to do this for a non\-default GHC version: ``` final: prev: { haskell = prev.haskell // { packages = prev.haskell.packages // { ghc98 = prev.haskell.packages.ghc98.override (old: { overrides = hfinal: hprev: { libvirt-hs = final.haskell.lib.overrideCabal hprev.libvirt-hs (old: { libraryPkgconfigDepends = (old.libraryPkgconfigDepends or []) ++ [ final.libvirt ]; }); }; }); }; }; } ``` I've used Nixpkgs long enough that I'm used to this sort of thing by now, but this is not the sort of user experience that I would confidently recommend to a coworker if they were on the fence about Nix\. This poor user experience is \(in my view\) a big contributor to why Nix gets consistently sidelined as companies grow[2](https://haskellforall.com/2026/06/ergonomic-overrides-for-nixpkgs#user-content-fn-2)\. So I asked myself: how*would*I have preferred to write that last example? The answer I converged upon was something along these lines: ``` haskell.packages.ghc98.override.overrides = libvirt-hs.overrideCabal.libraryPkgconfigDepends ++= [ libvirt ]; ``` There's plenty we can workshop there, but I still think something like that would be much less intimidating to a newcomer\. Additionally, that syntax is much more autocomplete\-friendly\! A user could write: ``` haskell.packages.ghc98.override.<TAB> ``` … and their editor could suggest all of the available overrides for completion, which is very doable because even without a type system you can query the list of available overrides[3](https://haskellforall.com/2026/06/ergonomic-overrides-for-nixpkgs#user-content-fn-3): ``` nix-repl> haskell.packages.ghc98.override.__functionArgs { all-cabal-hashes = false; buildHaskellPackages = false; compilerConfig = true; configurationArm = true; configurationCommon = true; configurationDarwin = true; configurationJS = true; configurationNix = true; configurationWindows = true; ghc = false; haskellLib = false; initialPackages = true; lib = false; nonHackagePackages = true; overrides = true; packageSetConfig = true; pkgs = false; stdenv = false; } ``` Moreover, this simpler syntax also suggests a simpler type system\. Instead of thinking in terms of override functions and overlays I believe we*should*just be thinking in terms of attribute paths and safe operations on those attribute paths\. Structuring all overrides in that way would greatly simplify the type system \(both the implementation and the user experience\)\. ## Implementation I have not yet built any such programming language\. What I*did*do, though, is to release an[`override\-utils`package](https://github.com/Gabriella439/override-utils)that approximates that idealized interface in pure Nix\. I created this package as a starting point to prove out the idea before even thinking about embarking on a larger programming language project\. For example, using`override\-utils`the above example would be written as: ``` final: override { haskell.packages.ghc98.override.overrides = set (hfinal: override { libvirt-hs.overrideCabal.libraryPkgconfigDepends = append [ final.libvirt ]; }); } ``` … which is already pretty similar to the original idealized interface I proposed: ``` haskell.packages.ghc98.override.overrides = libvirt-hs.overrideCabal.libraryPkgconfigDepends ++= [ libvirt ]; ``` … but there are a few important differences that I want to dig into\. ### Operations Something like`attribute \+\+= list`is not valid Nix, so`override\-utils`works around that by instead writing`attribute = append list`\. On the other hand`attribute = value`*is*valid Nix, but is*still*not supported by`override\-utils`and you have to instead do`attribute = set value`\. Why? Because otherwise there's no way to distinguish between this: ``` { foo = set { bar = 1; }; } # Replace `foo` with `{ bar = 1; }` ``` … and this: ``` { foo.bar = set 1; } # Replace `foo.bar` with `1` ``` Those should not mean the same thing, and you can see why with this minimal example: ``` nix-repl> :print override { foo = set { bar = 1; }; } { foo.baz = 2; } { foo = { bar = 1; }; } nix-repl> :print override { foo.bar = set 1; } { foo.baz = 2; } { foo = { bar = 1; baz = 2; }; } ``` The former completely replaces the`foo`attribute \(including the old`baz`sub\-attribute\), whereas the latter does not\. If you remove the`set`then both operations become`\{ foo\.bar = 1; \}`and we can no longer distinguish which one the user meant\. If we were to implement the same thing in a separate domain\-specific language \(i\.e\. not Nix\) then we could preserve that distinction and drop the need for`set`\. ### Qualification `override\-utils`also requires you to bring the`final`package set into scope when creating an overlay so that you can refer to other packages within the package set\. For example, this overlay: ``` final: prev: { x = prev.x + 1; y = final.x } ``` … corresponds to this`override`: ``` final: override { x = add 1; y = set final.x; } ``` In the idealized interface what I would prefer is that you can drop the`final`on the happy path and instead have naked references to other attributes when it's unambiguous\. For example, I'd like to be able to write this: ``` x += 1; y = x; ``` However, you would still have the option of qualifying these references when there would be multiple final package sets in scope \(e\.g\.`final`vs`hfinal`in our original example\) so that you could disambiguate which one you meant\. To expand on that last point, let's revisit the original idealized example: ``` haskell.packages.ghc98.override.overrides = libvirt-hs.overrideCabal.libraryPkgconfigDepends ++= [ libvirt ]; ``` I specifically chose this example as a starting point because in the absence of a qualifier I would like the`libvirt`reference in the idealized code to translate to`hfinal\.libvirt or final\.libvirt`in Nix \(which would then further evaluate to`final\.libvirt`since there is no`hfinal\.libvirt`\)\. However, I'd still like to user to be able to opt out of implicit qualification and make things explicit like this: ``` final: haskell.packages.ghc98.override.overrides = libvirt-hs.overrideCabal.libraryPkgconfigDepends ++= [ final.libvirt ]; ``` ### Nix\-compatible features The good news is that most other features I had in mind were totally doable in pure Nix, which is kind of cool\! In fact, the core implementation is actually very simple\. If you drop support for error messages and default values, you can implement a "dollar store`override\-utils`" in just a few lines of Nix code: ``` rec { override = argument: let adapt = name: value: let default = old: old // { "${name}" = override value old."${name}"; }; in { modify = value; "*" = map (override value); override = old: old.override (override value); overrideAttrs = old: old.overrideAttrs (override value); overrideDerivation = old: old.overrideDerivation (override value); overrideCabal = old: haskell.lib.overrideCabal old (override value); }."${name}" or default; in prev: lib.pipe prev (lib.mapAttrsToList adapt argument); modify = f : { modify = f; }; set = value: modify (_: value); add = value: modify (x: x + value); subtract = value: modify (x: x - value); append = suffix: modify (prefix: prefix ++ suffix); prepend = prefix: modify (suffix: prefix ++ suffix); # … add other utilities to taste … } ``` The[real implementation](https://github.com/Gabriella439/override-utils/blob/4f1aaa5384b99eae652a62f2d0e83e90e2eea63b/flake.nix#L17-L245)is slightly more complete and featureful, but the above snippet should give you some idea of how things work under the hood\. Those more familiar with functional programming lore might recognize that this is the Nix analog of[semantic editor combinators](http://conal.net/blog/posts/semantic-editor-combinators)\(the predecessors to[van Laarhoven lenses](https://www.twanvl.nl/blog/haskell/cps-functional-references)\)\. Or another way to think of it is that this is all equivalent to the[`Setter`type](https://hackage-content.haskell.org/package/lens-5.3.6/docs/Control-Lens-Setter.html#t:Setter)from[Haskell's`lens`package](https://hackage-content.haskell.org/package/lens)\. Attributes in the attribute path are equivalent to`Setter`s and chaining attributes is equivalent to composing`Setter`s \(and they both use "`\.`"\)\. ## Prior art > Edit: I'm embarrassed to say that I had missed[infuse\.nix](https://codeberg.org/amjoseph/infuse.nix)when researching prior art\. A few people pointed this out and I wrote up a quick comparison between`override\-utils`and`infuse\.nix`[here](https://discourse.nixos.org/t/ergonomic-overrides-for-nixpkgs/78081/3?u=gabriella439)\. The closest existing project in this space is[`dream2nix`](https://dream2nix.dev/), which \(among other things\) also exposes an improved interface to Nixpkgs \(based on the original[`drv\-parts`project](https://github.com/DavHau/drv-parts)\)\. Specifically,`dream2nix`exposes a NixOS module interface to Nixpkgs, where you can write code like this: ``` { config, lib, ... }: { pip.overrides.opencv-python = { env.autoPatchelfIgnoreMissingDeps = true; mkDerivation.buildInputs = [ pkgs.libglvnd pkgs.glib ]; }; } ``` … and for comparison, the equivalent overlay using`override\-utils`would be: ``` final: override { pythonPackageExtensions = append (pfinal: override { opencv-python.overridePythonAttrs = { env.autoPatchelfIgnoreMissingDeps = set true; buildInputs = append [ final.libglvnd final.glib ]; }; }); } ``` Kind of similar, right? That's why I added an Appendix where I compare the two projects for people who were interested\. ## Conclusion I think`override\-utils`is a useful contribution to the Nix ecosystem in its own right, but I also think we can still do much better\. In particular, in the conclusion of my post on[The hard part of type\-checking Nix](https://haskellforall.com/2022/03/the-hard-part-of-type-checking-nix)I summarized several paths forward for the language: > - **Solution A:**Don’t implement a type system for Nix … - **Solution B:**Only type\-check Nix “in the small” … - **Solution C:**Type\-check Nixpkgs overlays using a type system supporting row polymorphism … - **Solution D:**Implement the two sub\-languages in an external language In other words, implement the Nixpkgs overlay system and NixOS module system in a separate language that is not Nix so that overlays and modules are supported by the language along with a type system that natively understands these features\. Then you could compile this external language to ordinary Nix code that is compatible with the existing Nixpkgs overlay system or NixOS module system\. - **Solution E:**Like Solution D, but upstream these features into the Nix language … - **Solution F:**Like Solution D, but without Nix as an intermediate language … My best guess is that “Solution C” or “Solution D” are the two most promising approaches that strike the right balance between how difficult they are to implement and actually addressing what users want in a type\-checker\. Nowadays my thinking that "Solution D" is the correct path forward\. In other words, I think we need to build a domain\-specific language for Nixpkgs \(and NixOS\) that compiles to Nix\. This domain\-specific language should not only be typed but also should operate at a higher abstraction level where abstractions like overrides/overlays and modules have built\-in support from the language and type system\. `override\-utils`is just a small step in that direction: I created this package to prove out the idea that the correct abstraction for overrides/overlays is composable`Setter`s and safe operations on those`Setter`s\. If you're interested in learning more about the`override\-utils`package I recommend checking out the[`README`](https://github.com/Gabriella439/override-utils#override-utils)which contains the full documentation for the package \(it's not very complicated\)\. ## Appendix: comparison to`dream2nix` This section covers the biggest differences between`override\-utils`and`dream2nix`\. ### Incrementalism As far as I can tell,`dream2nix`is an "all or nothing" interface, meaning that`dream2nix`replaces your project's Nix entrypoint entirely with its own interface \(e\.g\.`dream2nix\.lib\.importPackages`or`dream2nix\.lib\.evalModules`\)\. This means that you can't incrementally port over an existing Nix project to`dream2nix`one piece at a time: you need to design your entire project around`dream2nix`from the beginning or migrate everything over wholesale\. In contrast,`override\-utils`generates overrides and overlays, meaning that you can use`override\-utils`anywhere an override or overlay is valid and no additional ceremony is required\. Also,[overlays are composable](https://haskellforall.com/2022/01/nixpkgs-overlays-are-monoids), which means that you can migrate things over to`override\-utils`one piece at a time\. For example, if you have a list of overlays: ``` import nixpkgs { inherit system; overlays = [ (import ./lib-overlay.nix) (import ./rust-overlay.nix) (import ./firefox-overlay.nix) (import ./git-cinnabar-overlay.nix) ]; } ``` Then any one of those individual overlays can be ported to use`override\-utils`in isolation without any other changes to the surrounding code\. This is true not just for top\-level overlays but also any nested overlay or override\. For example, suppose you have an overlay like this: ``` final: prev: { nix-serve-ng = prev.nix-serve-ng.overrideAttrs (old: { patches = (old.patches or []) ++ [ ./lix-compat.patch ]; }); } ``` Sure, you*could*replace the whole overlay with`override`: ``` final: override { nix-serve-ng.overrideAttrs.patches = append [ ./lix-compat.patch ]; } ``` … but you can also replace*just the override function*if you don't want to migrate the entire overlay: ``` final: prev: { nix-serve-ng = prev.nix-serve-ng.overrideAttrs (override { patches = append [ ./lix-compat.patch ]; }); } ``` In other words, you can migrate as much or as little as you want because I designed`override\-utils`to be as unobtrusive to adopt as possible\. ### Overlays The last section touches upon another difference:`dream2nix`is somewhat at odds with with the Nixpkgs overlay abstraction\. Overlay support was added to Nixpkgs with the intention that overlays would serve as the grand unifying abstraction, and`dream2nix`is promoting NixOS modules as an alternative grand unifying abstraction instead\. `override\-utils`instead aims to be more respectful of existing Nixpkgs design decisions and generates overlays \(and override functions\) instead of trying to replace or subsume them\. ### Option declarations `dream2nix`benefits in a major way from using the NixOS module system because you can provide option declarations and they provide multiple benefits: - you can get much better error messages if option definitions don't match the declared type - editors can make use of those option declarations to improve autocompletion support - option declarations can specify how to intelligently merge multiple definitions To expand upon the last point, if you define the following`dream2nix`option multiple times \(across more than one module\): ``` pip.overrides.opencv-python.mkDerivation.buildInputs = [ pkgs.libglvnd ]; pip.overrides.opencv-python.mkDerivation.buildInputs = [ pkgs.glib ]; ``` … then those multiple definitions are automatically merged into a single list because the declared type for`buildInputs`is a`list`and by default the`list`type appends multiple definitions together\. In`override\-utils`the merge logic is specified at the point where the option is defined \(e\.g\. using`append`, which also merges multiple definitions\) because in`override\-utils`there is no such thing as an option declaration or an option type\. ### Up\-front investment Related to the last point,`dream2nix`only works if you wrap your desired Nixpkgs functionality in a NixOS module interface and there is a*lot*of functionality in Nixpkgs that you would need to wrap in this way in order to comprehensively support all of Nixpkgs\. `override\-utils`requires no such up\-front investment\. All of Nixpkgs, today, works with`override\-utils`and it only took[≈200 lines of Nix code](https://github.com/Gabriella439/override-utils/blob/4f1aaa5384b99eae652a62f2d0e83e90e2eea63b/flake.nix#L17-L245)\(compared to ≈10,000 lines of`dream2nix`modules which still doesn't support anywhere near all of Nixpkgs\)\. ### Features `dream2nix`is doing much more than just provide a NixOS module interface to Nixpkgs;`dream2nix`is a more ambitious project that is attempting to unify all of the`\*2nix`utilities under a single umbrella/framework\. The NixOS module interface to Nixpkgs is just one component of that broader overarching vision to provide a cohesive interface for interoperating with every programming language from Nix\. On the other hand, I've been approaching this problem from a different standpoint, which is to think about Nixpkgs ergonomics from a programming languages perspective/background \(e\.g\. syntax, types, and semantics\)\. However, it's still fascinating that the two projects converged upon similar interfaces despite different overarching goals and implementations \. That suggests some sort of convergent evolution for Nix ergonomics\. 1. Note that`libvirt`is already a native dependency of`libvirt\-hs`so this override is not necessary, but I've had to do overrides similar to this in the past to work around broken builds in the past\. I just picked this as a contrived example that is still representative of real examples\.[↩](https://haskellforall.com/2026/06/ergonomic-overrides-for-nixpkgs#user-content-fnref-1) 2. It's so bad that one fintech company using Nix offered me $800K total compensation to tame their Python build\.[↩](https://haskellforall.com/2026/06/ergonomic-overrides-for-nixpkgs#user-content-fnref-2) 3. Thank you to whomever had the foresight to add support for that to the various`\.override\*`methods\.[↩](https://haskellforall.com/2026/06/ergonomic-overrides-for-nixpkgs#user-content-fnref-3)

Similar Articles

GuixPkgs: every Guix package, as a Nix flake

Lobsters Hottest

GuixPkgs is a project that makes every GNU Guix package available as a Nix flake, allowing users to mix Guix and Nixpkgs packages in a single flake. It uses guix-transfer to convert Guix derivations to Nix derivations and provides a binary cache to avoid rebuilding the entire Guix bootstrap.

Nixmac

Product Hunt

Nixmac is a tool that provides a plain English interface for Nix-darwin, simplifying Nix configuration on macOS.

Nix Flakes and their Guix Equivalents

Lobsters Hottest

A detailed comparison between Nix Flakes and their equivalents in the Guix package management system, covering dependency declarations, pinning, purity, outputs, development environments, and system configuration.

NNN Stack: NixOS, Niri, Noctalia

Lobsters Hottest

The NNN Stack combines NixOS, Niri compositor, and Noctalia shell to create a declarative, scrollable, and reproducible desktop environment, inviting users to contribute their dotfiles.