Skip to content
Oeiuwq Faith Blog OpenSource Porfolio

vic/with-inputs

A flake-inputs adapter for Nix projects that don't use `flake.nix`.

vic/with-inputs.json
{
"defaultBranch": "main",
"description": "A flake-inputs adapter for Nix projects that don't use `flake.nix`.",
"fullName": "vic/with-inputs",
"homepage": "https://dendritic.oeiuwq.com/ecosystem/with-inputs/",
"language": "Nix",
"name": "with-inputs",
"pushedAt": "2026-03-19T00:59:58Z",
"stargazersCount": 6,
"updatedAt": "2026-03-19T01:00:02Z",
"url": "https://github.com/vic/with-inputs"
}

Sponsor Vic Dendritic Nix License CI Status

with-inputs - A flake-inputs adapter for Nix projects that don’t use flake.nix.

Section titled “with-inputs - A flake-inputs adapter for Nix projects that don’t use flake.nix.”

with-inputs and vic’s dendritic libs made for you with Love++ and AI—. If you like my work, consider sponsoring

Provides exactly the same inputs resolution experience as real Nix flakes — follows, nested follows, per-sub-input overrides, inputs.self, and dependency introspection — using pre-fetched sources from npins, local checkouts, or any other source.

This library is not an inputs lock mechanism nor an inputs fetcher, for those we have plenty of options: npins, unflake, nixlock, nixtamal.

Download our default.nix into your project ./with-inputs.nix.

curl https://raw.githubusercontent.com/vic/with-inputs/refs/heads/main/default.nix -o with-inputs.nix

Or use npins or builtins.fetchTarball with a fixed revision of it.

Terminal window
npins add github vic with-inputs
default.nix
let
sources = import ./npins; # example with npins. use any other sources.
with-inputs = import sources.with-inputs sources {
# keep reading for follows and local inputs
};
outputs = inputs: { }; # your flake-like outputs function
in
with-inputs outputs

The second argument to with-inputs is an attribute set that can be used to drive input resolution, for example to use local checkout or to specify flake-like follows.

See [tests.nix]!(./tests.nix) and vic/vix:follows.nix for usage examples.

{
# Local checkout — loaded as a flake if a flake.nix is present
mylib.outPath = ./mylib;
# Local checkout with sub-input overrides applied when loading its flake.nix
someLib = { outPath = ./someLib; inputs.nixpkgs.follows = "nixpkgs"; };
# Direct import — value used as-is (function, module result, attrset, etc)
systems = import ./systems.nix;
# Top-level follows: alias one input to another
nixpkgs-stable.follows = "nixpkgs";
# Nested follows: traverse sub-inputs
something.follows = "a/b/c"; # → allInputs.a.inputs.b.inputs.c
# Empty follows: intentionally disconnect an input
unwanted.follows = "";
# Per-sub-input follows (mirrors flake.nix `inputs.foo.inputs.bar.follows`)
home-manager.inputs.nixpkgs.follows = "nixpkgs";
disko.inputs.nixpkgs.follows = "nixpkgs";
# Combined: keep the source, override some of its sub-inputs
someFlake = {
inputs.nixpkgs.follows = "nixpkgs";
inputs.utils.follows = "flake-utils";
};
# Takes the original sources.otherFlake and avoids flake call
otherFlake = source: source // { flake = false; };
}

This second argument can also be a function resolvedInputs -> flakeInputs, this is useful for example to shim dependencies like systems or flake-utils [example].

All standard inputs.self.* patterns work:

inputs.self # the assembled self
inputs.self.inputs # resolved inputs
inputs.self.inputs.self # circular, lazy-safe
inputs.self.inputs.nixpkgs # any resolved input
inputs.self.outputs # raw outputs attrset
inputs.self.nixosConfigurations # shorthand for inputs.self.outputs.nixosConfigurations

Every source with a flake.nix is fully resolved into the standard flake shape:

inputs.nixpkgs.outPath # store / local path
inputs.nixpkgs.sourceInfo # raw sourceInfo from sources
inputs.nixpkgs._type # "flake"
inputs.nixpkgs.inputs # nixpkgs' own resolved sub-inputs
inputs.nixpkgs.outputs # nixpkgs' outputs attrset (explicit)
inputs.nixpkgs.lib # shorthand — same as inputs.nixpkgs.outputs.lib

Dependency introspection works just like in flake-parts:

inputs.someFlake.inputs.nixpkgs # someFlake's resolved nixpkgs
inputs.someFlake.inputs.nixpkgs.lib # and its lib, etc.

When a follows target doesn’t exist in resolved inputs, the entry becomes null. Sub-flakes that declare that input as required will have their outputs call skipped (outputs stays {}), preventing evaluation errors — exactly like real flakes when a dependency is absent.

Input declarationMeaning
foo.outPath = ./path;Local checkout, loaded as flake if flake.nix present
foo = { outPath = ./path; inputs.dep.follows = "x"; };Local checkout with sub-input overrides
foo = import ./path;Direct value, used as-is
foo = pinned-source;Direct value from npins or similar
b.follows = "a";Alias to allInputs.a
b.follows = "a/x/y";Nested alias via .inputs. chain
b.follows = "";Empty — resolves to {}
a.inputs.b.follows = "x";Override sub-input b of source a
a.inputs.b.follows = "x/y";Override with nested follows
a = { inputs.b.follows = "x"; inputs.c.follows = "y"; };Meta-spec: keep source, override several sub-inputs

A value is treated as a spec (not a direct value) when its only keys are follows and/or inputs, and every inputs.* value is a { follows = …; }. Anything with outPath, lib, packages, _type, etc. is a direct value.

PR are welcome, make sure to run tests:

nix-unit tests.nix