easyOverlay Module
The easyOverlay module is a flake-parts-specific feature that auto-generates overlays from perSystem packages.
What is easyOverlay?
Instead of manually writing overlay functions, easyOverlay automatically generates overlays.default from your perSystem configuration.
Key benefit: Define packages once in perSystem, automatically get an overlay for free.
Basic Usage
{
inputs.flake-parts.url = "github:hercules-ci/flake-parts";
outputs = inputs@{ flake-parts, ... }:
flake-parts.lib.mkFlake { inherit inputs; } {
systems = [ "x86_64-linux" ];
# easyOverlay is a top-level module — import it here, NOT inside perSystem
imports = [ inputs.flake-parts.flakeModules.easyOverlay ];
perSystem = { config, pkgs, final, ... }: {
# Define your packages normally
packages = {
mylib = pkgs.callPackage ./mylib.nix { };
myapp = pkgs.callPackage ./myapp.nix {
# Use 'final' to reference overlaid packages
my-lib = final.mylib;
};
};
# Specify which packages should be in the overlay
overlayAttrs = {
inherit (config.packages) mylib myapp;
};
};
# Auto-generates: flake.overlays.default
};
}What happens:
- You define packages in
perSystem.packages - You specify which go in the overlay via
overlayAttrs - Flake-parts generates
overlays.defaultautomatically
final vs pkgs
With easyOverlay imported, you get two package set arguments:
pkgs - The “previous” package set (before your overlay):
packages.mylib = pkgs.callPackage ./mylib.nix { };Use this for normal package definitions.
final - The “final” package set (after your overlay):
packages.myapp = pkgs.callPackage ./myapp.nix {
my-lib = final.mylib; # Gets the overlaid version
};Use this when packages reference each other.
Why This Matters
# Top level — import once, outside perSystem
imports = [ inputs.flake-parts.flakeModules.easyOverlay ];
perSystem = { config, pkgs, final, ... }: {
packages = {
# Base library - uses pkgs
lib-base = pkgs.stdenv.mkDerivation {
name = "lib-base";
src = ./lib-base;
};
# Extension library - depends on base
lib-extended = pkgs.stdenv.mkDerivation {
name = "lib-extended";
src = ./lib-extended;
buildInputs = [ final.lib-base ]; # ✅ Use final
};
# Application - depends on both
myapp = pkgs.stdenv.mkDerivation {
name = "myapp";
src = ./app;
buildInputs = [
final.lib-base
final.lib-extended
];
};
};
# All packages available via overlay
overlayAttrs = {
inherit (config.packages) lib-base lib-extended myapp;
};
};Selective overlayAttrs
You don’t have to include all packages in the overlay:
# Top level — one import covers every perSystem module
imports = [ inputs.flake-parts.flakeModules.easyOverlay ];
perSystem = { config, pkgs, ... }: {
packages = {
# Public packages
public-tool = pkgs.callPackage ./public-tool.nix { };
public-lib = pkgs.callPackage ./public-lib.nix { };
# Internal tools (not in overlay)
internal-helper = pkgs.writeShellScript "helper" "...";
};
# Only public packages in overlay
overlayAttrs = {
inherit (config.packages) public-tool public-lib;
# internal-helper intentionally excluded
};
};Common Pitfalls
Using pkgs Instead of final
# ❌ WRONG - mylib not in pkgs yet
packages.myapp = pkgs.callPackage ./app.nix {
my-lib = pkgs.mylib; # undefined!
};
# ✅ CORRECT - use final
packages.myapp = pkgs.callPackage ./app.nix {
my-lib = final.mylib;
};Circular Dependencies
# ❌ WRONG - circular dependency
packages = {
a = pkgs.callPackage ./a.nix { b = final.b; };
b = pkgs.callPackage ./b.nix { a = final.a; };
};
# ✅ CORRECT - break the cycle
packages = {
a = pkgs.callPackage ./a.nix { };
b = pkgs.callPackage ./b.nix { a = final.a; };
};Forgetting to Import easyOverlay
# ❌ WRONG - final argument won't be available
perSystem = { pkgs, final, ... }: {
# Error: final is undefined
};
# ✅ CORRECT - import the module at the TOP LEVEL of your mkFlake module.
# Now every perSystem module receives final.
{
imports = [ inputs.flake-parts.flakeModules.easyOverlay ];
}
# ❌ ALSO WRONG - importing inside perSystem. easyOverlay is a top-level
# module; importing it inside perSystem tries to declare a perSystem option
# inside the perSystem submodule, and `inputs` isn't even a perSystem
# argument:
perSystem = { ... }: {
imports = [ inputs.flake-parts.flakeModules.easyOverlay ]; # error!
};Generated Overlay Structure
The generated overlay has this form:
overlays.default = final: prev: {
# Your overlayAttrs packages become available in pkgs
mylib = <derivation>;
myapp = <derivation>;
};This allows consumers to apply your overlay and get your packages in their pkgs.
Summary
Key concepts:
- Import
flakeModules.easyOverlayto enable - Define packages in
perSystem.packagesas normal - Specify overlay contents with
overlayAttrs - Use
finalwhen packages reference each other - Flake-parts auto-generates
overlays.default
When to use:
- Building libraries that other packages depend on
- Want your packages available in the pkgs namespace
- Need proper dependency resolution between packages
Related: Module arguments | Modular organization | Back to main guide