···11+# Starlight Starter Kit: Basics
22+33+[](https://starlight.astro.build)
44+55+```
66+pnpm create astro@latest -- --template starlight
77+```
88+99+> 🧑🚀 **Seasoned astronaut?** Delete this file. Have fun!
1010+1111+## 🚀 Project Structure
1212+1313+Inside of your Astro + Starlight project, you'll see the following folders and files:
1414+1515+```
1616+.
1717+├── public/
1818+├── src/
1919+│ ├── assets/
2020+│ ├── content/
2121+│ │ └── docs/
2222+│ └── content.config.ts
2323+├── astro.config.mjs
2424+├── package.json
2525+└── tsconfig.json
2626+```
2727+2828+Starlight looks for `.md` or `.mdx` files in the `src/content/docs/` directory. Each file is exposed as a route based on its file name.
2929+3030+Images can be added to `src/assets/` and embedded in Markdown with a relative link.
3131+3232+Static assets, like favicons, can be placed in the `public/` directory.
3333+3434+## 🧞 Commands
3535+3636+All commands are run from the root of the project, from a terminal:
3737+3838+| Command | Action |
3939+| :--------------------- | :----------------------------------------------- |
4040+| `pnpm install` | Installs dependencies |
4141+| `pnpm dev` | Starts local dev server at `localhost:4321` |
4242+| `pnpm build` | Build your production site to `./dist/` |
4343+| `pnpm preview` | Preview your build locally, before deploying |
4444+| `pnpm astro ...` | Run CLI commands like `astro add`, `astro check` |
4545+| `pnpm astro -- --help` | Get help using the Astro CLI |
4646+4747+## 👀 Want to learn more?
4848+4949+Check out [Starlight’s docs](https://starlight.astro.build/), read [the Astro documentation](https://docs.astro.build), or jump into the [Astro Discord server](https://astro.build/chat).
···11+---
22+import Default from '@astrojs/starlight/components/PageSidebar.astro';
33+import Ad from './Ad.astro';
44+---
55+66+<Default />
77+88+<Ad />
99+
+154
docs/src/components/Sidebar.astro
···11+---
22+import MobileMenuFooter from '@astrojs/starlight/components/MobileMenuFooter.astro';
33+import SidebarPersister from '@astrojs/starlight/components/SidebarPersister.astro';
44+import SidebarSublist from '@astrojs/starlight/components/SidebarSublist.astro';
55+66+const { sidebar, id } = Astro.locals.starlightRoute;
77+88+import { Icon } from '@astrojs/starlight/components';
99+1010+import TabbedContent from './tabs/TabbedContent.astro';
1111+import TabListItem from './tabs/TabListItem.astro';
1212+import TabPanel from './tabs/TabPanel.astro';
1313+1414+/** Get the icon for a group. Update the icon names in the array to change the icons associated with a group. */
1515+const getIcon = (index: number) =>
1616+ (['nix', 'open-book', 'rocket', 'puzzle', 'information', 'setting'] as const)[index];
1717+1818+/** Convert a group label to an `id` we can use to identify tab panels. */
1919+// The id is prefixed to avoid clashing with existing heading IDs on the page.
2020+const makeId = (label: string) => '__tab-' + label.toLowerCase().replaceAll(/\s+/g, '-');
2121+2222+/** Determine if an array of sidebar items contains the current page. */
2323+const isCurrent = (sidebar: SidebarEntry[]): boolean =>
2424+ sidebar
2525+ .map((entry) => (entry.type === 'link' ? entry.isCurrent : isCurrent(entry.entries)))
2626+ .some((entry) => entry === true);
2727+2828+---
2929+<SidebarPersister>
3030+ <TabbedContent class="tabbed-sidebar">
3131+ <Fragment slot="tab-list">
3232+ {
3333+ sidebar.map(({ label, entries }, index) => (
3434+ <TabListItem id={makeId(label)} initial={isCurrent(entries)} class="tab-item">
3535+ <Icon class="icon" name={getIcon(index)} /> {label}
3636+ </TabListItem>
3737+ ))
3838+ }
3939+ </Fragment>
4040+ {
4141+ sidebar.map(({ label, entries }) => (
4242+ <TabPanel id={makeId(label)} initial={isCurrent(entries)}>
4343+ <SidebarSublist sublist={entries} />
4444+ </TabPanel>
4545+ ))
4646+ }
4747+ </TabbedContent>
4848+</SidebarPersister>
4949+5050+<div class="md:sl-hidden">
5151+ <MobileMenuFooter />
5252+</div>
5353+5454+<style>
5555+ /** Add "EN" to the end of sidebar items with the `fallback` class. */
5656+ :global(.fallback)::after {
5757+ content: 'EN';
5858+ vertical-align: super;
5959+ font-size: 0.75em;
6060+ font-weight: 700;
6161+ }
6262+6363+ /** Align sponsors at sidebar bottom when there is room. */
6464+ .desktop-footer {
6565+ margin-top: auto;
6666+ }
6767+6868+ /** Always show the scrollbar gutter. */
6969+ :global(.sidebar-pane) {
7070+ overflow-y: scroll;
7171+ }
7272+7373+ /* Styles for the custom tab switcher. */
7474+ .tabbed-sidebar {
7575+ /* Layout variables */
7676+ --tab-switcher-border-width: 1px;
7777+ --tab-switcher-padding: calc(0.25rem - var(--tab-switcher-border-width));
7878+ --tab-item-border-radius: 0.5rem;
7979+ --tab-switcher-border-radius: calc(
8080+ var(--tab-item-border-radius) + var(--tab-switcher-padding) + var(--tab-switcher-border-width)
8181+ );
8282+8383+ /* Color variables */
8484+ --tab-switcher-border-color: var(--sl-color-hairline-light);
8585+ --tab-switcher-background-color: var(--sl-color-gray-7, var(--sl-color-gray-6));
8686+ --tab-switcher-text-color: var(--sl-color-gray-3);
8787+ --tab-switcher-text-color--active: var(--sl-color-white);
8888+ --tab-switcher-icon-color: var(--sl-color-gray-4);
8989+ --tab-switcher-icon-color--active: var(--sl-color-text-accent);
9090+ --tab-item-background-color--hover: var(--sl-color-gray-6);
9191+ --tab-item-background-color--active: var(--sl-color-black);
9292+ }
9393+ /* Dark theme variations */
9494+ :global([data-theme='dark']) .tabbed-sidebar {
9595+ --tab-switcher-text-color: var(--sl-color-gray-2);
9696+ --tab-switcher-icon-color: var(--sl-color-gray-3);
9797+ --tab-item-background-color--hover: var(--sl-color-gray-5);
9898+ }
9999+100100+ @media (min-width: 50rem) {
101101+ /* Dark theme variations with the desktop sidebar visible */
102102+ :global([data-theme='dark']) .tabbed-sidebar {
103103+ --tab-switcher-background-color: var(--sl-color-black);
104104+ --tab-item-background-color--hover: var(--sl-color-gray-6);
105105+ --tab-item-background-color--active: var(--sl-color-gray-6);
106106+ }
107107+ }
108108+109109+ .tabbed-sidebar.tab-list {
110110+ border: var(--tab-switcher-border-width) solid var(--tab-switcher-border-color);
111111+ border-radius: var(--tab-switcher-border-radius);
112112+ display: flex;
113113+ flex-direction: column;
114114+ gap: 0.25rem;
115115+ padding: var(--tab-switcher-padding);
116116+ background-color: var(--tab-switcher-background-color);
117117+ margin-bottom: 1.5rem;
118118+ }
119119+120120+ .tab-item :global(a) {
121121+ border: var(--tab-switcher-border-width) solid transparent;
122122+ border-radius: var(--tab-item-border-radius);
123123+ display: flex;
124124+ align-items: center;
125125+ gap: 0.5rem;
126126+ padding: calc(0.5rem - var(--tab-switcher-border-width));
127127+ background-clip: padding-box;
128128+ line-height: var(--sl-line-height-headings);
129129+ text-decoration: none;
130130+ color: var(--tab-switcher-text-color);
131131+ font-weight: 600;
132132+ }
133133+134134+ .tab-item :global(a:hover) {
135135+ color: var(--tab-switcher-text-color--active);
136136+ background-color: var(--tab-item-background-color--hover);
137137+ }
138138+ .tab-item :global(a[aria-selected='true']) {
139139+ border-color: var(--tab-switcher-border-color);
140140+ color: var(--tab-switcher-text-color--active);
141141+ background-color: var(--tab-item-background-color--active);
142142+ }
143143+144144+ .icon {
145145+ margin: 0.25rem;
146146+ color: var(--tab-switcher-icon-color);
147147+ }
148148+ .tab-item :global(a:hover) .icon {
149149+ color: inherit;
150150+ }
151151+ .tab-item :global(a[aria-selected='true']) .icon {
152152+ color: var(--tab-switcher-icon-color--active);
153153+ }
154154+</style>
···11+MIT License
22+33+Copyright (c) 2022 withastro
44+55+Permission is hereby granted, free of charge, to any person obtaining a copy
66+of this software and associated documentation files (the "Software"), to deal
77+in the Software without restriction, including without limitation the rights
88+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
99+copies of the Software, and to permit persons to whom the Software is
1010+furnished to do so, subject to the following conditions:
1111+1212+The above copyright notice and this permission notice shall be included in all
1313+copies or substantial portions of the Software.
1414+1515+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1616+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1717+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
1818+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
1919+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
2020+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
2121+SOFTWARE.
+23
docs/src/components/tabs/TabListItem.astro
···11+---
22+import type { HTMLAttributes } from 'astro/types';
33+44+export interface Props {
55+ /** Unique ID for the tab panel this item links to. */
66+ id: string;
77+ /** Mark this item as visible when the page loads. */
88+ initial?: boolean;
99+ /** Additional class names to apply to the `<li>` */
1010+ class?: string;
1111+}
1212+1313+const { id, initial } = Astro.props;
1414+const linkAttributes: HTMLAttributes<'a'> = initial
1515+ ? { 'data-initial': 'true', 'aria-selected': 'true' }
1616+ : {};
1717+---
1818+1919+<li class:list={Astro.props.class}>
2020+ <a href={'#' + id} class="tab-link" {...linkAttributes}>
2121+ <slot />
2222+ </a>
2323+</li>
+44
docs/src/components/tabs/TabPanel.astro
···11+---
22+import type { HTMLAttributes } from 'astro/types';
33+44+export interface Props {
55+ id: string;
66+ initial?: boolean;
77+}
88+const { id, initial } = Astro.props;
99+const attributes: HTMLAttributes<'div'> = initial ? { 'data-initial': 'true' } : {};
1010+---
1111+1212+<div {id} {...attributes}>
1313+ <slot />
1414+</div>
1515+1616+<style>
1717+ /*
1818+ These styles avoid layout shift on page load.
1919+ We don’t want to hide all tabs forever in case JS never loads,
2020+ so instead we hide them initially with an animation that jumps
2121+ from hidden to visible after 10s. Usually JS will run before
2222+ 10s at which point we’ll rely on the `hidden` attribute and
2323+ toggle off the animation using `role='tabpanel'`. Both these
2424+ attributes are injected by the JS.
2525+ */
2626+2727+ div {
2828+ animation: tab-panel-appear 10s steps(2, jump-none) 1;
2929+ }
3030+3131+ div[data-initial],
3232+ div[role='tabpanel'] {
3333+ animation: none;
3434+ }
3535+3636+ @keyframes tab-panel-appear {
3737+ from {
3838+ /* `content-visibility` is set as well as `display` to work around a Firefox
3939+ bug where animations containing only `display: none` don’t play. */
4040+ display: none;
4141+ content-visibility: hidden;
4242+ }
4343+ }
4444+</style>
+173
docs/src/components/tabs/TabbedContent.astro
···11+---
22+import TabListItem from './TabListItem.astro';
33+44+export interface Props {
55+ /**
66+ * List of content for the tab list.
77+ *
88+ * To use more complex mark-up for the tab list, pass `<TabListItem>`s
99+ * inside a `<Fragment slot="tab-list">`.
1010+ */
1111+ tabs?: { label: string; id: string; initial?: boolean }[];
1212+ /** Enable default styles for the tab list and panels. */
1313+ styled?: boolean;
1414+ /** Additional class names to apply to `.tab-list` and `.panels`. */
1515+ class?: string;
1616+}
1717+1818+const { tabs, styled } = Astro.props as Props;
1919+---
2020+2121+<tabbed-content>
2222+ <ul class:list={['tab-list', Astro.props.class, { 'tab-list--styled': styled }]}>
2323+ <slot name="tab-list">
2424+ {
2525+ tabs?.map((tab) => (
2626+ <TabListItem id={tab.id} initial={tab.initial}>
2727+ {tab.label}
2828+ </TabListItem>
2929+ ))
3030+ }
3131+ </slot>
3232+ </ul>
3333+3434+ <div class:list={['panels', Astro.props.class, { 'panels--styled': styled }]}>
3535+ <slot />
3636+ </div>
3737+</tabbed-content>
3838+3939+<style>
4040+ .tab-list {
4141+ list-style: none;
4242+ padding: 0;
4343+ }
4444+ .tab-list--styled {
4545+ display: flex;
4646+ margin-top: -1px;
4747+ overflow-x: auto;
4848+ overflow-y: hidden;
4949+ }
5050+ @media (min-width: 72em) {
5151+ .tab-list--styled {
5252+ justify-content: space-between;
5353+ margin-top: 0;
5454+ padding: 1px;
5555+ }
5656+ }
5757+5858+ .panels--styled {
5959+ padding-left: 1px;
6060+ padding-right: 1px;
6161+ }
6262+</style>
6363+6464+<script>
6565+ class Tabs extends HTMLElement {
6666+ readonly id = Math.floor(Math.random() * 10e10).toString(32);
6767+ count = 0;
6868+ TabStore: Set<HTMLElement>[] = [];
6969+ PanelStore: Set<HTMLElement>[] = [];
7070+7171+ constructor() {
7272+ super();
7373+7474+ // Get relevant elements and collections
7575+ const panels = this.querySelectorAll<HTMLElement>('.panels > [id]');
7676+ const tablist = this.querySelector('.tab-list')!;
7777+ const tabs = tablist.querySelectorAll('a');
7878+7979+ // Add the tablist role to the first <ul> in the .tabbed container
8080+ tablist.setAttribute('role', 'tablist');
8181+8282+ let initialTab = 0;
8383+8484+ // Add semantics are remove user focusability for each tab
8585+ Array.prototype.forEach.call(tabs, (tab: HTMLElement, i: number) => {
8686+ tab.setAttribute('role', 'tab');
8787+ tab.setAttribute('id', this.id + 'tab' + this.count++);
8888+ tab.setAttribute('tabindex', '-1');
8989+ tab.parentElement?.setAttribute('role', 'presentation');
9090+ if (!this.TabStore[i]) this.TabStore.push(new Set());
9191+ this.TabStore[i].add(tab);
9292+ if ('initial' in tab.dataset && tab.dataset.initial !== 'false') initialTab = i;
9393+9494+ // Handle clicking of tabs for mouse users
9595+ const onClick = (e: MouseEvent) => {
9696+ e.preventDefault();
9797+ const currentTab = tablist.querySelector('[aria-selected]');
9898+ if (e.currentTarget !== currentTab) {
9999+ this.switchTab(e.currentTarget as HTMLElement, i);
100100+ }
101101+ };
102102+ tab.addEventListener('click', onClick);
103103+ tab.addEventListener('auxclick', onClick);
104104+105105+ // Handle keydown events for keyboard users
106106+ tab.addEventListener('keydown', (e) => {
107107+ // Get the index of the current tab in the tabs node list
108108+ const index: number = Array.prototype.indexOf.call(tabs, e.currentTarget);
109109+ // Work out which key the user is pressing and
110110+ // Calculate the new tab's index where appropriate
111111+ const dir =
112112+ e.key === 'ArrowLeft'
113113+ ? index - 1
114114+ : e.key === 'ArrowRight'
115115+ ? index + 1
116116+ : e.key === 'Home'
117117+ ? 0
118118+ : e.key === 'End'
119119+ ? tabs.length - 1
120120+ : null;
121121+ if (dir !== null) {
122122+ e.preventDefault();
123123+ if (tabs[dir]) this.switchTab(tabs[dir], dir);
124124+ }
125125+ });
126126+ });
127127+128128+ // Add tab panel semantics and hide them all
129129+ Array.prototype.forEach.call(panels, (panel: HTMLElement, i: number) => {
130130+ panel.setAttribute('role', 'tabpanel');
131131+ panel.setAttribute('tabindex', '-1');
132132+ panel.setAttribute('aria-labelledby', tabs[i].id);
133133+ panel.hidden = true;
134134+ if (!this.PanelStore[i]) this.PanelStore.push(new Set());
135135+ this.PanelStore[i].add(panel);
136136+ });
137137+138138+ // Activate and reveal the initial tab panel
139139+ tabs[initialTab].removeAttribute('tabindex');
140140+ tabs[initialTab].setAttribute('aria-selected', 'true');
141141+ panels[initialTab].hidden = false;
142142+ }
143143+144144+ // The tab switching function
145145+ switchTab(newTab: HTMLElement, index: number) {
146146+ this.TabStore.forEach((store) =>
147147+ store.forEach((oldTab) => {
148148+ oldTab.removeAttribute('aria-selected');
149149+ oldTab.setAttribute('tabindex', '-1');
150150+ })
151151+ );
152152+ this.TabStore[index].forEach((newTab) => {
153153+ // Make the active tab focusable by the user (Tab key)
154154+ newTab.removeAttribute('tabindex');
155155+ // Set the selected state
156156+ newTab.setAttribute('aria-selected', 'true');
157157+ });
158158+159159+ this.PanelStore.forEach((store) =>
160160+ store.forEach((oldPanel) => {
161161+ oldPanel.hidden = true;
162162+ })
163163+ );
164164+ this.PanelStore[index].forEach((newPanel) => {
165165+ newPanel.hidden = false;
166166+ });
167167+168168+ newTab.focus();
169169+ }
170170+ }
171171+172172+ customElements.define('tabbed-content', Tabs);
173173+</script>
···11+---
22+title: How it Works
33+description: The module system, option schema, write-flake, and the outputs function.
44+---
55+66+import { Aside } from '@astrojs/starlight/components';
77+88+99+flake-file integrates input definition as part of your Nix modules.
1010+1111+It provides a set of module options under the `flake-file` namespace that describe the contents of `flake.nix`.
1212+1313+<Aside title="Schema based inputs!">
1414+Unlike unstable-nix flakes, flake-file inputs DO have a **typed** schema, one that
1515+mirrors Nix Flakes but actually implemented using the Nix module system.
1616+</Aside>
1717+1818+When you run `nix run .#write-flake`, it evaluates those options and writes the file.
1919+2020+2121+```
2222+edit modules/*.nix # declare flake-file options
2323+ ↓
2424+nix run .#write-flake # extracts inputs and serializes
2525+ ↓
2626+flake.nix # updated and locked
2727+```
2828+2929+## The Module System
3030+3131+Any Nix module can set `flake-file.*` options. Because this is the standard Nix module system, all the usual tools apply:
3232+3333+- `lib.mkDefault` — set a value that can be overridden by other modules.
3434+- `lib.mkForce` — force a value regardless of what other modules set.
3535+- `lib.mkIf` — conditionally define an input.
3636+- Attribute merging — multiple files contribute to the same input set.
3737+3838+```nix
3939+{ inputs, lib, ... }: {
4040+ flake-file.inputs.nixpkgs.url = lib.mkDefault "github:NixOS/nixpkgs/nixpkgs-unstable";
4141+ flake-file.inputs.nixpkgs-lib.follows = "nixpkgs";
4242+}
4343+```
4444+4545+## The Option Schema
4646+4747+Options mirror the flake schema and extend it further:
4848+4949+| Namespace | Purpose |
5050+|---|---|
5151+| `flake-file.description` | Flake description string |
5252+| `flake-file.nixConfig` | Nix binary cache and substituter config |
5353+| `flake-file.inputs.<name>.*` | Input declarations (url, follows, flake, ref, …) |
5454+| `flake-file.outputs` | Literal Nix expression for the outputs function |
5555+| `flake-file.write-hooks` | Commands run after writing |
5656+| `flake-file.check-hooks` | Commands run during check |
5757+| `flake-file.prune-lock.*` | Automatic lock flattening |
5858+5959+See the [Options Reference](/reference/options) for the complete table.
6060+6161+## Apps: write-flake and flake check
6262+6363+**`nix run .#write-flake`** collects inputs, serialises the merged options to valid Nix syntax, and writes `flake.nix`. It also runs any configured `write-hooks`.
6464+6565+**`nix flake check`** verifies that `flake.nix` is up to date with the current module state. If it is stale, the check fails. Add this to CI.
6666+6767+## The outputs Function
6868+6969+`flake-file.outputs` is a *literal Nix string* that becomes the body of the `outputs` function in `flake.nix`. It defaults to:
7070+7171+```nix
7272+inputs: import ./outputs.nix inputs
7373+```
7474+7575+This keeps `flake.nix` as a pure dependency manifest. All Nix logic lives in `outputs.nix`. See the [Outputs guide](/guides/outputs) for details.
···11+---
22+title: The reason for flake-file
33+description: Who it's for, the problem it solves, and what makes it different.
44+---
55+66+## Your flake.nix is not real Nix
77+88+Have you ever tried to use a let expression or interpolate some common string into an `input.url` ?
99+You will be happy to discover that your [flake.nix file is a subset of Nix](https://github.com/NixOS/nix/issues/4945).
1010+The only real Nix on it is the content of the `outputs` function.
1111+1212+1313+## Flake examples are monolithic.
1414+1515+Isn't it weird to you that almost all examples of Nix Flakes that newcomers can find in the wild
1616+are big monolithic files?
1717+1818+It is like teaching Node people to use package.json to write the full application on it.
1919+2020+`flake.nix` is a distribution asset. And should be used as such: as a **dependencies manifest**.
2121+2222+Make yourself a favor right now -this guide will wait for you to be back-:
2323+move your outputs function into `outputs.nix` and use something like:
2424+2525+```nix
2626+# flake.nix
2727+{
2828+ inputs = { };
2929+ outputs = inputs: import ./outputs.nix inputs;
3030+}
3131+```
3232+3333+3434+## Sharing input requirements across stable/unstable Nix.
3535+3636+Even if some people use unstable flakes, others should not be forced out of stable Nix.
3737+3838+Each module defines inputs and flake-file can extract to whatever input-locking backend you need.
3939+Be it `flake.nix`, `unflake.nix`, `npins`.
4040+4141+## Everybody `.follows`
4242+4343+Even when Flakes inputs are locked for reproducibility, most people override their transitive
4444+dependencies because otherwise you end up downloading zillion different nixpkgs revisions.
4545+The build guarantee is broken because upstream flake author did not expected a new
4646+possibly-incompatible version of its dependencies.
4747+4848+Instead of using `.follows` which is external to the Nix language, flake-file uses
4949+the Nix module system, that already solves this class of problems for NixOS configurations —
5050+flake-file brings the same `lib.mkDefault`/`lib.mkForce` approach to `flake.nix` itself.
5151+5252+## How flake-file Helps
5353+5454+Instead of editing `flake.nix` directly, you declare inputs as **module options** — in any Nix module, close to where the dependency is used. flake-file then generates a clean, up-to-date `flake.nix` from those declarations.
5555+5656+```nix
5757+# modules/my-tool.nix
5858+{ lib, ... }: {
5959+ flake-file.inputs.my-tool.url = lib.mkDefault "github:owner/my-tool";
6060+}
6161+```
6262+6363+Running `nix run .#write-flake` materialises all declared inputs into `flake.nix`. A flake check enforces the file stays in sync with the modules.
6464+6565+## Key Properties
6666+6767+- **Modular:** Each module declares only its own dependencies.
6868+- **Composable:** Modules can be shared across projects — including their input declarations.
6969+- **Backend-agnostic:** The same module options generate `flake.nix`, `unflake.nix`, or `npins/` depending on the chosen backend.
7070+- **Standard Nix:** Uses the Nix module system — `lib.mkDefault`, priority overrides, conditional inputs — all work as expected.
7171+7272+## Real-world Usage
7373+7474+Many Dendritic Nix layouts use flake-file because it helps with localization of concerns.
7575+Inputs are declared near where they are used, frequently in the same module.
7676+Decomissining the module also removes the inputs.
7777+7878+Even when flake-file was born for flakes, its author [vic/vix](https://github.com/vic/vix) uses flake-file without flakes. The [`dev/`](https://github.com/vic/flake-file/blob/main/dev) directory in this repo eats its own cooking. [More examples on GitHub](https://github.com/search?q=%22vic%2Fflake-file%22+language%3ANix&type=code).
+87
docs/src/content/docs/guides/flake-modules.mdx
···11+---
22+title: flakeModules
33+description: All built-in flakeModules provided by flake-file.
44+---
55+66+flake-file ships several flakeModules. Import only what you need.
77+88+## `flakeModules.default`
99+1010+The base module. Provides all `flake-file.*` options, the `write-flake` app, and flake checks.
1111+1212+```nix
1313+{ inputs, ... }: {
1414+ imports = [ inputs.flake-file.flakeModules.default ];
1515+}
1616+```
1717+1818+Source: [`modules/default.nix`](https://github.com/vic/flake-file/tree/main/modules/default.nix)
1919+2020+## `flakeModules.import-tree`
2121+2222+Adds [import-tree](https://github.com/vic/import-tree) — auto-import all `.nix` files from a directory.
2323+2424+Source: [`modules/import-tree.nix`](https://github.com/vic/flake-file/tree/main/modules/import-tree.nix)
2525+2626+## `flakeModules.dendritic`
2727+2828+A batteries-included setup for [Dendritic](https://vic.github.io/dendrix/Dendritic.html) projects. Includes:
2929+3030+- `flakeModules.default`
3131+- `flakeModules.import-tree`
3232+- [flake-parts](https://github.com/hercules-ci/flake-parts)
3333+- Default `outputs` set to `inputs: inputs.flake-parts.lib.mkFlake { inherit inputs; } (inputs.import-tree ./modules)`
3434+3535+```nix
3636+{ inputs, lib, ... }: {
3737+ imports = [ inputs.flake-file.flakeModules.dendritic ];
3838+3939+ flake-file = {
4040+ description = "My Awesome Flake";
4141+ inputs.nixpkgs.url = lib.mkDefault "github:NixOS/nixpkgs/nixpkgs-unstable";
4242+ inputs.nixpkgs-lib.follows = "nixpkgs";
4343+ };
4444+}
4545+```
4646+4747+Source: [`modules/dendritic/default.nix`](https://github.com/vic/flake-file/tree/main/modules/dendritic/default.nix)
4848+4949+## `lib.flakeModules.flake-parts-builder`
5050+5151+Integrates [flake-parts-builder](https://github.com/tsandrini/flake-parts-builder). Loads parts from a directory and reads each part's `_meta/` as flake-file configs.
5252+5353+```nix
5454+{ inputs, ... }: {
5555+ imports = [
5656+ (inputs.flake-file.lib.flakeModules.flake-parts-builder ./flake-parts)
5757+ ];
5858+}
5959+```
6060+6161+See [flake-parts-builder guide](/guides/flake-parts-builder) for details.
6262+6363+Source: [`modules/flake-parts-builder/default.nix`](https://github.com/vic/flake-file/tree/main/modules/flake-parts-builder/default.nix)
6464+6565+## `flakeModules.allfollow`
6666+6767+Enables automatic `flake.lock` flattening using [spikespaz/allfollow](https://github.com/spikespaz/allfollow).
6868+6969+Source: [`modules/prune-lock/allfollow.nix`](https://github.com/vic/flake-file/tree/main/modules/prune-lock/allfollow.nix)
7070+7171+## `flakeModules.nix-auto-follow`
7272+7373+Enables automatic `flake.lock` flattening using [fzakaria/nix-auto-follow](https://github.com/fzakaria/nix-auto-follow).
7474+7575+Source: [`modules/prune-lock/nix-auto-follow.nix`](https://github.com/vic/flake-file/tree/main/modules/prune-lock/nix-auto-follow.nix)
7676+7777+## `flakeModules.npins`
7878+7979+Defines `flake-file` options for [npins](https://github.com/andir/npins)-based environments. Exposes `write-npins`. Supports `github`, `gitlab`, `channel`, `tarball`, and `git` schemes. Respects `follows` for deduplication. Prunes stale pins automatically.
8080+8181+Source: [`modules/npins.nix`](https://github.com/vic/flake-file/tree/main/modules/npins.nix)
8282+8383+## `flakeModules.unflake`
8484+8585+Defines `flake-file` options for [unflake](https://codeberg.org/goldstein/unflake)-based environments. Exposes `write-unflake`.
8686+8787+Source: [`modules/unflake.nix`](https://github.com/vic/flake-file/tree/main/modules/unflake.nix)
···11+---
22+title: flake-parts-builder
33+description: Incrementally add and remove flake-parts templates with flake-parts-builder.
44+---
55+66+## What is flake-parts-builder?
77+88+[flake-parts-builder](https://github.com/tsandrini/flake-parts-builder) lets you add or remove templated flake-parts modules (called *parts*) at any time — not just at project initialisation.
99+1010+flake-file integrates with it so that each part can also declare its own `flake-file` inputs via a `_meta/` file.
1111+1212+## Setup
1313+1414+```nix
1515+{ inputs, ... }: {
1616+ imports = [
1717+ (inputs.flake-file.lib.flakeModules.flake-parts-builder ./flake-parts)
1818+ ];
1919+}
2020+```
2121+2222+This:
2323+2424+1. Bootstraps flake-parts-builder from `./flake-parts`.
2525+2. Auto-loads Nix modules from `./flake-parts`.
2626+3. Reads each part's `./flake-parts/<part>/_meta/` directory as flake-file config.
2727+2828+## Adding parts
2929+3030+```shell
3131+nix run github:vic/flake-parts-builder/write-meta -- add --write-meta --parts systems,treefmt $PWD
3232+```
3333+3434+> **Important:** Use `github:vic/flake-parts-builder/write-meta` until [flake-parts-builder#60](https://github.com/tsandrini/flake-parts-builder/pull/60) is merged. This branch writes each part's `_meta/` file so that flake-file can manage inputs declared by the part.
3535+3636+> **Warning:** Only use the `add` subcommand. The `init` subcommand overwrites `flake.nix`, which is managed by flake-file.
3737+3838+## Regenerate after adding parts
3939+4040+```shell
4141+nix run ".#write-flake"
4242+nix flake check
4343+```
4444+4545+## How _meta/ works
4646+4747+Each part directory may contain a `_meta/` subdirectory with Nix files that set `flake-file.*` options. When the `flake-parts-builder` module is imported, these files are automatically included in the module evaluation, so the part's dependencies are declared alongside the part itself.
+52
docs/src/content/docs/guides/hooks.mdx
···11+---
22+title: Hooks
33+description: Run commands after writing flake.nix or during flake check.
44+---
55+66+## Overview
77+88+flake-file provides two hook lists:
99+1010+- **`flake-file.write-hooks`** — commands executed after `write-flake` writes `flake.nix`.
1111+- **`flake-file.check-hooks`** — commands executed during `nix flake check`.
1212+1313+Each hook is an attrset with an `index` (for ordering) and a command.
1414+1515+## write-hooks
1616+1717+Use write-hooks to trigger follow-up actions whenever `flake.nix` is regenerated — for example, updating a lock file or reformatting.
1818+1919+```nix
2020+{ pkgs, ... }: {
2121+ flake-file.write-hooks = [
2222+ {
2323+ index = 10;
2424+ exec = "${pkgs.nix}/bin/nix flake update";
2525+ }
2626+ ];
2727+}
2828+```
2929+3030+Hooks run in ascending `index` order, so you can coordinate between modules without knowing the full list.
3131+3232+## check-hooks
3333+3434+Use check-hooks to add extra validation during `nix flake check` — for example, checking that a derived file is up to date.
3535+3636+```nix
3737+{ pkgs, ... }: {
3838+ flake-file.check-hooks = [
3939+ {
4040+ index = 10;
4141+ exec = "${pkgs.my-linter}/bin/my-linter --check .";
4242+ }
4343+ ];
4444+}
4545+```
4646+4747+## Practical uses
4848+4949+- Run `nix flake update` automatically after regeneration.
5050+- Reformat `flake.nix` with a formatter (see `flake-file.formatter`).
5151+- Validate generated files in CI via check-hooks.
5252+- Trigger [allfollow or nix-auto-follow](/guides/lock-flattening) to flatten the lock file.
+51
docs/src/content/docs/guides/lock-flattening.mdx
···11+---
22+title: Lock Flattening
33+description: Automatically flatten flake.lock to reduce dependency duplication.
44+---
55+66+## The Problem
77+88+Nix flakes fetch every input's own lock file, which can result in many copies of the same package set (e.g. multiple `nixpkgs` versions) being downloaded and stored. Flattening forces all inputs to share common dependencies.
99+1010+## Built-in Modules
1111+1212+flake-file ships support for two established flattening tools:
1313+1414+### allfollow
1515+1616+[spikespaz/allfollow](https://github.com/spikespaz/allfollow) automatically adds `follows` entries for all transitive inputs.
1717+1818+```nix
1919+{ inputs, ... }: {
2020+ imports = [ inputs.flake-file.flakeModules.allfollow ];
2121+}
2222+```
2323+2424+Source: [`modules/prune-lock/allfollow.nix`](https://github.com/vic/flake-file/tree/main/modules/prune-lock/allfollow.nix)
2525+2626+### nix-auto-follow
2727+2828+[fzakaria/nix-auto-follow](https://github.com/fzakaria/nix-auto-follow) detects duplicate inputs and injects `follows` for them.
2929+3030+```nix
3131+{ inputs, ... }: {
3232+ imports = [ inputs.flake-file.flakeModules.nix-auto-follow ];
3333+}
3434+```
3535+3636+Source: [`modules/prune-lock/nix-auto-follow.nix`](https://github.com/vic/flake-file/tree/main/modules/prune-lock/nix-auto-follow.nix)
3737+3838+## How it integrates
3939+4040+Both modules configure `flake-file.prune-lock` options, which instruct `write-flake` to run the flattener after writing `flake.nix`. You can also use the raw options for a custom tool:
4141+4242+```nix
4343+{ pkgs, ... }: {
4444+ flake-file.prune-lock = {
4545+ enable = true;
4646+ program = pkgs: pkgs.my-flattener;
4747+ };
4848+}
4949+```
5050+5151+See the [Options Reference](/reference/options) for the full `prune-lock` schema.
+60
docs/src/content/docs/guides/outputs.mdx
···11+---
22+title: The outputs Function
33+description: How flake-file.outputs works and best practices for using it.
44+---
55+66+## What is flake-file.outputs?
77+88+The `flake-file.outputs` option is a **literal Nix string** that is placed verbatim as the `outputs` attribute in the generated `flake.nix`:
99+1010+```nix
1111+# generated flake.nix (simplified)
1212+{
1313+ outputs = <your flake-file.outputs value here>;
1414+}
1515+```
1616+1717+## The Default
1818+1919+The default value is:
2020+2121+```nix
2222+inputs: import ./outputs.nix inputs
2323+```
2424+2525+This is deliberately minimal — it delegates all logic to `outputs.nix`, keeping `flake.nix` as a pure dependency manifest.
2626+2727+## Why Keep the Default?
2828+2929+`flake.nix` is a generated file. If you embed complex Nix logic in `flake-file.outputs`, you are putting business logic in a string option, which is hard to read, impossible to type-check, and easy to break.
3030+3131+The recommended split:
3232+3333+- **`flake.nix`** — generated, declares inputs and nixConfig only.
3434+- **`outputs.nix`** — hand-written, contains all your Nix logic.
3535+3636+## Overriding for a One-liner
3737+3838+The only reasonable case for overriding `outputs` is a legitimate one-liner — for example, the `dendritic` module sets:
3939+4040+```nix
4141+flake-file.outputs = "inputs: inputs.flake-parts.lib.mkFlake { inherit inputs; } (inputs.import-tree ./modules)";
4242+```
4343+4444+This is still a single expression with no embedded logic.
4545+4646+## What You Cannot Do
4747+4848+You cannot assign a Nix function value to `flake-file.outputs`. A Nix function cannot be serialised to a string. This option exists *only* for a literal code string.
4949+5050+```nix
5151+# WRONG — this will fail
5252+flake-file.outputs = inputs: inputs.flake-parts.lib.mkFlake { inherit inputs; } { };
5353+```
5454+5555+Always set it to a string:
5656+5757+```nix
5858+# Correct
5959+flake-file.outputs = ''inputs: inputs.flake-parts.lib.mkFlake { inherit inputs; } { }'';
6060+```
+65
docs/src/content/docs/guides/templates.mdx
···11+---
22+title: Templates
33+description: Ready-made project templates for flake-file.
44+---
55+66+flake-file ships several Nix flake templates. Initialise any of them with:
77+88+```shell
99+nix flake init -t github:vic/flake-file#<template>
1010+```
1111+1212+## `default`
1313+1414+A minimal, explicit setup without extra framework dependencies.
1515+1616+```nix
1717+# flake-parts module — see templates/default
1818+{ inputs, ... }: {
1919+ imports = [ inputs.flake-file.flakeModules.default ];
2020+2121+ flake-file.inputs = {
2222+ flake-file.url = "github:vic/flake-file";
2323+ flake-parts.url = "github:hercules-ci/flake-parts";
2424+ nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
2525+ };
2626+2727+ systems = inputs.nixpkgs.lib.systems.flakeExposed;
2828+}
2929+```
3030+3131+> Run `nix run .#write-flake` to generate `flake.nix`.
3232+3333+## `dendritic`
3434+3535+Includes `flakeModules.dendritic` — the recommended starting point for new [Dendritic](https://vic.github.io/dendrix/Dendritic.html) projects. Comes with import-tree, flake-parts, and sensible defaults.
3636+3737+```shell
3838+nix flake init -t github:vic/flake-file#dendritic
3939+nix run ".#write-flake"
4040+nix flake check
4141+```
4242+4343+## `parts`
4444+4545+Uses `lib.flakeModules.flake-parts-builder` for projects that manage their flake-parts modules with [flake-parts-builder](https://github.com/tsandrini/flake-parts-builder).
4646+4747+## `npins`
4848+4949+For **non-flake** (stable Nix) environments. Uses [npins](https://github.com/andir/npins) to pin inputs. Supports channels, GitHub, GitLab, tarballs, and git repos. Recommended for new non-flake projects.
5050+5151+```shell
5252+nix flake init -t github:vic/flake-file#npins
5353+```
5454+5555+## `unflake`
5656+5757+For **non-flake** (stable Nix) environments. Uses [goldstein/unflake](https://codeberg.org/goldstein/unflake) to pin inputs.
5858+5959+```shell
6060+nix flake init -t github:vic/flake-file#unflake
6161+```
6262+6363+## Tips
6464+6565+> **Tip:** You can use the `write-flake` app as part of a devshell command or a git pre-commit hook to keep `flake.nix` always up to date.
+61
docs/src/content/docs/index.mdx
···11+---
22+title: flake-file
33+description: Generate flake.nix/unflake.nix/npins from modular Nix options.
44+template: splash
55+hero:
66+ tagline: |
77+ Your flake.nix, made modular and maintainable.<br />
88+ Use the <strong>real</strong> Nix language to define your inputs.
99+ image:
1010+ html: |
1111+ <img width="400" height="400" alt="ouroboros" src="https://github.com/user-attachments/assets/2c0b2208-2f65-4fb3-b9df-5cf78dcad0e7" />
1212+ actions:
1313+ - text: Quick Start
1414+ link: /tutorials/quick-start
1515+ icon: right-arrow
1616+ - text: What is flake-file?
1717+ link: /explanation/what-is-flake-file
1818+ variant: minimal
1919+ - text: Overview
2020+ link: /overview
2121+ variant: minimal
2222+ - text: Source Code
2323+ link: https://github.com/vic/flake-file
2424+ icon: github
2525+ variant: secondary
2626+---
2727+2828+import { Card, CardGrid, Aside } from '@astrojs/starlight/components';
2929+3030+**flake-file** lets you generate a clean, maintainable `flake.nix` from modular Nix options.
3131+3232+Define inputs close to where they are used — in any Nix module — then with a single command, write `flake.nix` or `unflake.nix` or `npins`.
3333+3434+3535+<Aside type="tip" title="With or without flakes.">
3636+Despite the original flake-oriented name, `flake-file` now **also** works without flakes on stable Nix.
3737+</Aside>
3838+3939+<Aside title="Use the real Nix language on your inputs">
4040+Ever wanted to interpolate a string just to discover inputs are **NOT REAL** Nix expressions?
4141+4242+**Q**: What language is flake.nix written in? [nix#4945](https://github.com/NixOS/nix/issues/4945)
4343+4444+**A**: It's a subset of Nix that doesn't allow computation in the flake metadata attributes.
4545+</Aside>
4646+4747+<CardGrid>
4848+ <Card title="Modular Inputs" icon="puzzle">
4949+ Define `flake.nix` inputs in separate modules using the Nix module system and the **real** Nix language features. Use `lib.mkDefault` or interpolate Nix expressions, etc.
5050+ </Card>
5151+ <Card title="Single Command Regeneration" icon="rocket">
5252+ Run `nix run .#write-flake` to regenerate `flake.nix` from your options. A flake check enforces that files stay in sync.
5353+ Also provides `nix-shell` commands for stable Nix world.
5454+ </Card>
5555+ <Card title="Lock Flattening" icon="list-format">
5656+ Built-in support for automatic `flake.lock` flattening via `allfollow` or `nix-auto-follow`.
5757+ </Card>
5858+ <Card title="Multiple Backends" icon="setting">
5959+ Generate `flake.nix`, `unflake.nix`, or `npins/` from the same module options. Switch backends without rewriting your modules.
6060+ </Card>
6161+</CardGrid>
+76
docs/src/content/docs/overview.mdx
···11+---
22+title: Overview
33+description: Everything flake-file.
44+---
55+66+import { Card, CardGrid, LinkButton } from '@astrojs/starlight/components';
77+88+## Core Idea
99+1010+flake-file treats `flake.nix` as a generated artifact. You declare inputs and settings as Nix module options, then regenerate the file whenever those declarations change.
1111+1212+<CardGrid>
1313+ <Card title="What is flake-file?" icon="open-book">
1414+ Understand the problem it solves, who it's for, and how it compares to a plain `flake.nix`.
1515+ <LinkButton href="/explanation/what-is-flake-file" variant="minimal" icon="right-arrow">Learn More</LinkButton>
1616+ </Card>
1717+ <Card title="How it Works" icon="setting">
1818+ The module system, option schema, the `write-flake` app, and the `outputs` function.
1919+ <LinkButton href="/explanation/how-it-works" variant="minimal" icon="right-arrow">Learn More</LinkButton>
2020+ </Card>
2121+</CardGrid>
2222+2323+## Getting Started
2424+2525+<CardGrid>
2626+ <Card title="Quick Start" icon="rocket">
2727+ Bootstrap a new project with the `dendritic` template and generate your first `flake.nix` in minutes.
2828+ <LinkButton href="/tutorials/quick-start" variant="minimal" icon="right-arrow">Learn More</LinkButton>
2929+ </Card>
3030+ <Card title="Migration Guide" icon="pencil">
3131+ Step-by-step guide for adopting flake-file in an existing flake-parts project.
3232+ <LinkButton href="/tutorials/migrate" variant="minimal" icon="right-arrow">Learn More</LinkButton>
3333+ </Card>
3434+ <Card title="Bootstrapping" icon="add-document">
3535+ Generate a first `flake.nix` without any existing flake using a one-shot bootstrap command.
3636+ <LinkButton href="/tutorials/bootstrap" variant="minimal" icon="right-arrow">Learn More</LinkButton>
3737+ </Card>
3838+</CardGrid>
3939+4040+## Guides
4141+4242+<CardGrid>
4343+ <Card title="flakeModules" icon="puzzle">
4444+ All built-in flakeModules: `default`, `dendritic`, `import-tree`, `npins`, `unflake`, `allfollow`, `nix-auto-follow`, and `flake-parts-builder`.
4545+ <LinkButton href="/guides/flake-modules" variant="minimal" icon="right-arrow">Learn More</LinkButton>
4646+ </Card>
4747+ <Card title="Templates" icon="document">
4848+ Ready-made project templates: `default`, `dendritic`, `parts`, `npins`, and `unflake`.
4949+ <LinkButton href="/guides/templates" variant="minimal" icon="right-arrow">Learn More</LinkButton>
5050+ </Card>
5151+ <Card title="Outputs Function" icon="seti:config">
5252+ How `flake-file.outputs` works and why you should keep it as the default one-liner.
5353+ <LinkButton href="/guides/outputs" variant="minimal" icon="right-arrow">Learn More</LinkButton>
5454+ </Card>
5555+ <Card title="Hooks" icon="list-format">
5656+ Run commands after writing or during check with `write-hooks` and `check-hooks`.
5757+ <LinkButton href="/guides/hooks" variant="minimal" icon="right-arrow">Learn More</LinkButton>
5858+ </Card>
5959+ <Card title="Lock Flattening" icon="random">
6060+ Automatically flatten `flake.lock` with `allfollow` or `nix-auto-follow`.
6161+ <LinkButton href="/guides/lock-flattening" variant="minimal" icon="right-arrow">Learn More</LinkButton>
6262+ </Card>
6363+ <Card title="flake-parts-builder" icon="add-document">
6464+ Incrementally add and remove flake-parts templates with flake-parts-builder integration.
6565+ <LinkButton href="/guides/flake-parts-builder" variant="minimal" icon="right-arrow">Learn More</LinkButton>
6666+ </Card>
6767+</CardGrid>
6868+6969+## Reference
7070+7171+<CardGrid>
7272+ <Card title="All Options" icon="document">
7373+ Complete reference for every `flake-file.*` module option.
7474+ <LinkButton href="/reference/options" variant="minimal" icon="right-arrow">Learn More</LinkButton>
7575+ </Card>
7676+</CardGrid>
+78
docs/src/content/docs/reference/options.mdx
···11+---
22+title: Options Reference
33+description: Complete reference for all flake-file.* module options.
44+---
55+66+All options are set under the `flake-file` namespace in any flake-parts module.
77+88+See the source: [`modules/options/default.nix`](https://github.com/vic/flake-file/blob/main/modules/options/default.nix)
99+1010+## Top-level Options
1111+1212+| Option | Type | Description |
1313+|---|---|---|
1414+| `flake-file.description` | `str` | Flake description string |
1515+| `flake-file.nixConfig` | `attrs` | Nix binary cache / substituter config (free-form attrset) |
1616+| `flake-file.outputs` | `str` | Literal Nix expression for the `outputs` function |
1717+| `flake-file.formatter` | `pkgs -> drv` | Function producing the formatter used on the generated file |
1818+| `flake-file.do-not-edit` | `str` | Header comment written at the top of the generated file |
1919+2020+## Input Options
2121+2222+Each input is declared under `flake-file.inputs.<name>`.
2323+2424+| Option | Type | Description |
2525+|---|---|---|
2626+| `flake-file.inputs.<name>.url` | `str` | Source URL — e.g. `github:owner/repo` |
2727+| `flake-file.inputs.<name>.type` | `str` | Reference type — `github`, `path`, `git`, etc. |
2828+| `flake-file.inputs.<name>.owner` | `str` | Owner (for typed VCS refs) |
2929+| `flake-file.inputs.<name>.repo` | `str` | Repository name |
3030+| `flake-file.inputs.<name>.path` | `str` | Local path reference |
3131+| `flake-file.inputs.<name>.id` | `str` | Flake registry id |
3232+| `flake-file.inputs.<name>.dir` | `str` | Subdirectory within repo or path |
3333+| `flake-file.inputs.<name>.narHash` | `str` | NAR hash pin |
3434+| `flake-file.inputs.<name>.rev` | `str` | Commit hash pin |
3535+| `flake-file.inputs.<name>.ref` | `str` | Branch or tag pin |
3636+| `flake-file.inputs.<name>.host` | `str` | Custom host for git forges |
3737+| `flake-file.inputs.<name>.submodules` | `bool` | Whether to fetch git submodules |
3838+| `flake-file.inputs.<name>.flake` | `bool` | Is the input a flake? (default: `true`) |
3939+| `flake-file.inputs.<name>.follows` | `str` | Follow another input's resolved value |
4040+| `flake-file.inputs.<name>.inputs.<dep>.follows` | `str` | Nested transitive follow |
4141+| `flake-file.inputs.<name>.inputs.<dep>.inputs...` | `…` | Recursively follow deeper transitive deps |
4242+4343+### Example
4444+4545+```nix
4646+flake-file = {
4747+ description = "my awesome flake";
4848+ nixConfig = {};
4949+ inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
5050+ inputs.nixpkgs-lib.follows = "nixpkgs";
5151+ inputs.my-tool.flake = false;
5252+ inputs.my-tool.url = "github:owner/my-tool";
5353+ inputs.dep.inputs.nixpkgs.follows = "nixpkgs";
5454+};
5555+```
5656+5757+## Hook Options
5858+5959+| Option | Type | Description |
6060+|---|---|---|
6161+| `flake-file.write-hooks` | `listOf hookType` | Ordered hooks run after `write-flake` writes the file |
6262+| `flake-file.check-hooks` | `listOf hookType` | Ordered hooks run during flake check |
6363+6464+Each hook has:
6565+6666+| Field | Description |
6767+|---|---|
6868+| `index` | Integer — lower index runs first |
6969+| `exec` | Shell command string to execute |
7070+7171+## Lock Flattening Options
7272+7373+| Option | Type | Description |
7474+|---|---|---|
7575+| `flake-file.prune-lock.enable` | `bool` | Enable automatic `flake.lock` pruning after writing |
7676+| `flake-file.prune-lock.program` | `pkgs -> drv` | Function building the lock-pruning executable |
7777+7878+See [Lock Flattening guide](/guides/lock-flattening) for usage.
+38
docs/src/content/docs/tutorials/bootstrap.mdx
···11+---
22+title: Bootstrapping
33+description: Generate a first flake.nix without any existing flake.
44+---
55+66+## The Bootstrap Command
77+88+You can create a `flake.nix` from scratch — without running inside an existing flake — using a one-shot bootstrap command:
99+1010+```shell
1111+# Write a minimal bootstrap module
1212+echo '{ flake-file.inputs.flake-file.url = "github:vic/flake-file"; }' > bootstrap.nix
1313+1414+# Generate flake.nix
1515+nix-shell https://github.com/vic/flake-file/archive/refs/heads/main.zip \
1616+ -A flake-file.sh \
1717+ --run write-flake \
1818+ --arg modules ./bootstrap.nix
1919+```
2020+2121+Replace `write-flake` with `write-inputs`, `write-unflake`, or `write-npins` to target a different backend.
2222+2323+## Using a modules directory
2424+2525+`bootstrap.nix` can also be a directory. All `.nix` files in it will be auto-imported:
2626+2727+```shell
2828+nix-shell https://github.com/vic/flake-file/archive/refs/heads/main.zip \
2929+ -A flake-file.sh \
3030+ --run write-flake \
3131+ --arg modules ./modules
3232+```
3333+3434+This is useful when you already have module files and just need the initial `flake.nix` to be generated before Nix can evaluate the flake itself.
3535+3636+## Next Steps
3737+3838+After generating `flake.nix`, continue with the standard [Quick Start](/tutorials/quick-start) flow.
+85
docs/src/content/docs/tutorials/migrate.mdx
···11+---
22+title: Migration Guide
33+description: Adopt flake-file in an existing flake-parts project.
44+---
55+66+## Prerequisites
77+88+Your project must already use [flake-parts](https://flake.parts). If it doesn't, migrate to flake-parts first.
99+1010+## Steps
1111+1212+### 1. Add the flake-file input
1313+1414+In your current `flake.nix`, add:
1515+1616+```nix
1717+flake-file.url = "github:vic/flake-file";
1818+```
1919+2020+### 2. Move outputs to outputs.nix
2121+2222+Copy the body of your `outputs` function into a new file `./outputs.nix`:
2323+2424+```nix
2525+# outputs.nix
2626+inputs: inputs.flake-parts.lib.mkFlake { inherit inputs; } {
2727+ imports = [
2828+ ./modules/inputs.nix
2929+ # Add more modules here as you split things up.
3030+ ];
3131+}
3232+```
3333+3434+### 3. Create inputs.nix
3535+3636+Copy your current `flake.nix` input declarations into a flake-parts module:
3737+3838+```nix
3939+# modules/inputs.nix
4040+{ inputs, ... }: {
4141+ imports = [
4242+ inputs.flake-file.flakeModules.default
4343+ ];
4444+4545+ flake-file = {
4646+ description = "Your flake description";
4747+ inputs = {
4848+ flake-file.url = "github:vic/flake-file";
4949+ # ... all your original inputs here
5050+ };
5151+ nixConfig = { }; # if you had any
5252+ };
5353+}
5454+```
5555+5656+> **Important:** Run `git add` so that the new files are visible to Nix.
5757+5858+### 4. Back up and regenerate
5959+6060+```shell
6161+cp flake.nix flake.nix.bak
6262+nix run .#write-flake
6363+```
6464+6565+### 5. Verify
6666+6767+```shell
6868+cat flake.nix # inspect the generated file
6969+nix flake check # verify everything is in sync
7070+rm flake.nix.bak # clean up if happy
7171+```
7272+7373+## Splitting inputs across modules
7474+7575+Once migrated, you can distribute input declarations to the modules that use them:
7676+7777+```nix
7878+# modules/my-tool.nix
7979+{ inputs, lib, ... }: {
8080+ flake-file.inputs.my-tool.url = lib.mkDefault "github:owner/my-tool";
8181+ imports = lib.optionals (inputs ? my-tool) [ inputs.my-tool.flakeModule ];
8282+}
8383+```
8484+8585+Remove the entry from `modules/inputs.nix` and regenerate. The result is the same `flake.nix`, but each module is self-contained.
+65
docs/src/content/docs/tutorials/quick-start.mdx
···11+---
22+title: Quick Start
33+description: Bootstrap a new project with flake-file in minutes.
44+---
55+66+## Prerequisites
77+88+- Nix with flakes enabled.
99+- An internet connection to fetch the template.
1010+1111+## Create a new project
1212+1313+Use the `dendritic` template — it sets up a full [flake-parts](https://flake.parts) + [import-tree](https://github.com/vic/import-tree) environment with flake-file already wired in.
1414+1515+```shell
1616+nix flake init -t github:vic/flake-file#dendritic
1717+```
1818+1919+## Generate flake.nix
2020+2121+```shell
2222+nix run ".#write-flake"
2323+```
2424+2525+This evaluates your modules, merges all `flake-file.*` options, and writes `flake.nix`.
2626+2727+```shell
2828+cat flake.nix # inspect the generated file
2929+```
3030+3131+## Verify
3232+3333+```shell
3434+nix flake check
3535+```
3636+3737+This confirms that `flake.nix` is up to date with the current module state. Run this in CI.
3838+3939+## Add your first input
4040+4141+Open (or create) a module under `./modules/`. Declare any dependency close to where it is used:
4242+4343+```nix
4444+# modules/my-tool.nix
4545+{ inputs, lib, ... }: {
4646+ flake-file.inputs.my-tool.url = lib.mkDefault "github:owner/my-tool";
4747+4848+ # Use the input once it's in flake.nix
4949+ imports = lib.optionals (inputs ? my-tool) [
5050+ inputs.my-tool.flakeModule
5151+ ];
5252+}
5353+```
5454+5555+Then regenerate:
5656+5757+```shell
5858+nix run ".#write-flake"
5959+```
6060+6161+## Next Steps
6262+6363+- [Migration Guide](/tutorials/migrate) — adopt flake-file in an existing project.
6464+- [flakeModules](/guides/flake-modules) — explore all built-in modules.
6565+- [Options Reference](/reference/options) — every `flake-file.*` option explained.