Overlays
Most package managers treat their package set as a fixed database — you can install what is in it, but modifying or replacing a package requires forking the manager or maintaining a separate repository. Nixpkgs takes a different approach: the entire package set is a Nix value, and overlays let you transform it with ordinary Nix functions. You can add new packages, replace existing ones, change build options, or patch sources, and the result is a first-class package set indistinguishable from the original.
What an overlay is
An overlay is a function that takes two arguments and returns an attribute set of packages:
final: prev: {
# packages to add or replace
}
previs the package set before this overlay was applied. Use it to access the original version of a package you are modifying. When changing a package, you always want to useprevto avoid infinite recursion.finalis the package set after all overlays have been applied. Use it when you need a dependency that may itself have been modified by another overlay. In almost all cases you want to consume packages fromfinal, generally in the form offinal.callPackage.
The distinction matters: reaching for prev.somePackage gives you the
unmodified package; reaching for final.somePackage gives you the
post-overlay version. Using final for your own package’s dependencies ensures
the full overlay chain is respected.
How overlays are applied
Nixpkgs applies overlays through a fixed-point fold. Each overlay’s result is
merged with the package set and the accumulation is passed as prev to the
next overlay. Once all overlays have been applied, the fully merged set is fed back as
final. This is the same fixed-point mechanism that makes the NixOS module
system work — apparent circular references resolve correctly because Nix is
lazy.
The practical consequence is that overlays compose: you can have multiple overlays that each build on each other’s results, and the order they are applied in is well-defined.
The (simplified) implementation of overlays:
{ overlays }:
let
# Fold over all overlays so that `prev:` is applied to each. Results in a single `self: { ... }` recursive function.
toFix = lib.foldl' (lib.flip lib.extends) (self: { }) overlays
# Apply the recursive function to itself, converging to a "fixed point", works because nix is lazy
fix = f: let x = f x in x;
# "Fixing" results in a single package set, which we commonly refer to as "pkgs"
pkgs = fix toFix;
in
pkgs
Adding a package
The simplest overlay adds a package that is not in nixpkgs:
final: prev: {
myapp = final.callPackage ./myapp.nix { };
}
Using final.callPackage (not prev.callPackage) ensures that myapp’s
dependencies are resolved from the fully overlaid package set.
Modifying an existing package
To change build options on an existing package, use overrideAttrs:
final: prev: {
hello = prev.hello.overrideAttrs (old: {
doCheck = false;
});
}
prev.hello is used here deliberately — you want the original package as the
base, not a potentially already-modified version. Doing hello = final.hello.overrideAttrs ( ... ); would result
in infinite recursion.
Overriding dependencies
override changes the inputs (generally other packages) that callPackage passed to a package:
final: prev: {
# Build ffmpeg against our custom version of x264
ffmpeg = prev.ffmpeg.override {
x264 = final.x264-custom;
};
}
Composing multiple overlays
Overlays are just values in a list, so composing them is straightforward:
pkgs = import nixpkgs {
system = "x86_64-linux";
overlays = [
(final: prev: { myapp = final.callPackage ./myapp.nix { }; })
(final: prev: { mytool = final.callPackage ./mytool.nix { }; })
];
};
They are applied in list order, left to right, so a later overlay can build on
packages introduced by an earlier one. lib.composeManyExtensions can be used to
fold many overlays into what appears to be a single overlay.
Overlays in a flake
The conventional way to expose an overlay from a flake is through the
overlays output:
outputs = { self, nixpkgs }: {
overlays.default = final: prev: {
myapp = final.callPackage ./myapp.nix { };
};
};
Consumers apply it when importing nixpkgs:
pkgs = import nixpkgs {
system = "x86_64-linux";
overlays = [ anotherFlake.overlays.default ];
};
Within the same flake, self.overlays.default refers to the overlay:
outputs = { self, nixpkgs }:
let
pkgs = import nixpkgs {
system = "x86_64-linux";
overlays = [ self.overlays.default ];
};
in {
overlays.default = final: prev: {
myapp = final.callPackage ./myapp.nix { };
};
packages.x86_64-linux.myapp = pkgs.myapp;
};
A note on naming: self/super
Older nixpkgs code and documentation uses self: super: for the overlay
arguments instead of final: prev:. They mean exactly the same thing — self
is final and super is prev. The final/prev naming was adopted later
as it is more descriptive, and is now the convention in new code.