Nix flake organization
Keep
as the public output layer and
as the implementation layer. The flake tree names, routes, and re-exports outputs; feature behavior, module bodies, package derivations, app scripts, and helper logic live under
.
Apply
codebase-design within those boundaries:
keep each feature's implementation together and extract only to hide complexity
or enable useful reuse. Nix output adapters are meaningful public entrypoints;
additional internal forwarding files need their own purpose. Preserve Nix's
roles rather than imposing TypeScript
and
directories.
When to use
- A user asks to reorganize or refactor a Nix flake layout.
- Flake outputs are crowded into or mixed with implementation code.
- NixOS, nix-darwin, Home Manager, or flake-parts modules contain business logic directly in output wiring files.
- A repo needs a clear split between public flake API and implementation internals.
When NOT to use
- The task is only to add one package, app, or module without changing repo layout.
- The repo is not a Nix flake.
- The user explicitly wants a different established project layout preserved.
Canonical prompt
Use this improved prompt when turning the request into an implementation plan:
Reorganize this repo so its public flake surface lives in a top-level
directory:
,
,
, and
flake/modules/{flake-parts,home-manager,darwin,nixos}
where applicable. Keep
as a minimal entrypoint and keep every file under
thin: it may declare output names, import implementation files, compose flake-parts modules, and adapt public calling conventions, but it must not contain derivation logic, app scripts, option/config bodies, service behavior, or helper implementations. Keep those implementation details together in feature-oriented
paths. Preserve existing output attribute names during structural moves; follow the repository's compatibility policy when changing the public interface. Verify with
,
, and representative package/module evals or builds.
Target shape
Prefer this structure, adapting names to the existing repo:
text
flake.nix
flake/default.nix
flake/apps/default.nix
flake/packages/default.nix
flake/lib/default.nix
flake/checks/default.nix # if checks exist
flake/devShells/default.nix # if devShells exist
flake/overlays/default.nix # if overlays exist
flake/modules/flake-parts/default.nix
flake/modules/home-manager/default.nix
flake/modules/darwin/default.nix
flake/modules/nixos/default.nix
src/<name>/app.nix
src/<name>/package.nix
src/<name>/lib.nix
src/<name>/modules/flake-parts.nix
src/<name>/modules/home-manager.nix
src/<name>/modules/darwin.nix
src/<name>/modules/nixos.nix
flake/modules/flake-parts
belongs beside the platform module families when flake-parts modules exist, even if the user's shorthand example omits it.
Feature-oriented means grouping by domain concept, not by output type. If a concept
has a package, app, and NixOS module, keep them under
. If a package has no associated modules,
src/<pkg-name>/package.nix
is fine. When a repo is already organized by platform, preserving that structure under
is acceptable, but do not mechanically recreate the whole
tree under
.
Thin layer rule
| Location | Allowed | Not allowed |
|---|
| Inputs, minimal , import | Package/module implementation |
| App output names and imports | Shell scripts, wrappers, runtime behavior |
| Package output names and imports | , overlays, build logic |
| Public helper re-exports | Helper implementations |
| Check output names and imports | Test harness implementation |
| Dev shell output names and imports | Tool setup logic, shell hooks |
| Overlay output names and imports | Package overrides and build logic |
flake/modules/flake-parts
| flake-parts imports and module composition | build logic, derivations, app scripts |
| Public module exports and imports | Options, , assertions, services |
| All implementation details | Public output schema decisions |
Thin does not mean empty. A thin file can adapt calling conventions, pass
,
, or
, and preserve public attribute names. It should be understandable without reading implementation details.
Keep configured file limits. If a cohesive module exceeds one, first look for an
independent responsibility; otherwise document a narrow increase or exception.
Keep options, defaults, assertions, and configuration local when they explain
one feature, even when that makes a module longer.
For module outputs, thin means the
flake/modules/<platform>/default.nix
file is a re-export point. It imports the full module from
and wires it into the public output attribute. It does not mean splitting options from config inside a single module; options and config stay together in
src/<name>/modules/<platform>.nix
.
nix
# flake/modules/nixos/default.nix
{ ... }:
{
flake.nixosModules.vpn = import ../../../src/vpn/modules/nixos.nix;
}
# src/vpn/modules/nixos.nix
{ config, lib, pkgs, ... }:
{
options.services.vpn = { ... };
config = lib.mkIf config.services.vpn.enable { ... };
}
In flake-parts repos,
often owns
,
,
, and
. Keep
composition thin in
flake/modules/flake-parts
; package derivations still belong in
, app behavior in
, and dev shell/check implementation in
. Do not define the same output through both direct
wiring and flake-parts
wiring.
Migration workflow
- Inventory current public outputs: , , , , , , , , , and flake-parts imports.
- Preserve the public output schema first. Move files without renaming output attributes unless the user requested a breaking change.
- If current outputs are interleaved in one file, first separate each output family enough that it can move independently; avoid a big-bang rewrite.
- Create the directories as output shims and move one output family at a time.
- Move implementation into feature-oriented ; avoid recreating the tree under unless the repo is already platform-oriented.
- Keep shared implementation in or , not in .
- Follow the repository's compatibility policy for renamed outputs. Keep aliases only when that policy requires them; otherwise update consumers and remove obsolete paths.
- Give outputs without an example directory, such as , their own thin shim or leave them in the nearest existing composition file; do not fold them into unrelated directories.
- Verify after each family move with , , and targeted builds/evals.
Common mistakes
- Moving into ; Nix expects at the repo root.
- Treating as the new implementation home instead of a thin public layer.
- Hiding implementation in because it feels like a shared bucket.
- Putting option declarations, , assertions, or services in .
- Forgetting that and are system-specific while module outputs usually are not.
- Putting build logic in
flake/modules/flake-parts
instead of importing implementations from .
- Creating both direct output wiring and flake-parts wiring for the same derivation without a deliberate compatibility reason.
- Creating circular imports between , , and .
- Changing public output names during a move-only refactor.
- Skipping verification because the change looks like only path shuffling.
Templated scripts
When packaging shell scripts, keep reusable script logic in its own source file instead of embedding long scripts inline in Nix strings. Use Nix only to substitute package-specific defaults.
Prefer nixpkgs-style
placeholders with
:
nix
writeTextFile {
name = "tool";
destination = "/bin/tool";
executable = true;
text = builtins.readFile (replaceVars ./tool.sh {
default_config = builtins.toJSON config;
});
}
In the script, group all Nix substitutions at the top and make runtime values overrideable by arguments or environment variables:
sh
default_config() {
cat <<'JSON'
@default_config@
JSON
}
: "${TOOL_JQ:=jq}"
tool_config() {
if [ -n "${TOOL_CONFIG_JSON:-}" ]; then
printf '%s\n' "$TOOL_CONFIG_JSON"
else
default_config
fi
}
This keeps scripts usable outside Nix, makes the Nix coupling easy to audit, avoids quote-heavy inline shell strings, and matches current nixpkgs conventions now that
has been replaced by
.
Review checklist
- files are mostly imports, attr names, re-exports, and public calling-convention adapters.
- contains derivations, scripts, options, config, services, assertions, and helpers.
- Existing public output names still work or have deliberate migration notes.
- Module families are separated: , , , and do not leak platform-specific logic into each other.
- Verification commands cover both system-specific outputs and module evaluation paths.
- Templated scripts live in standalone files, use placeholders via , and keep Nix-specific defaults grouped at the top with environment/argument overrides for reusable behavior.
Tools
None. This is a pure prompt and review skill. Use the repo's existing Nix commands for verification.
Reference
No separate reference files. Use the canonical prompt, thin layer rule, migration workflow, and review checklist above.