ndg is a fast, customizable and convenient utility for generating HTML documents and manpages for your Nix related projects from Markdown and options you use in any set of modules. It can be considered a more opinionated and more specialized MdBook, focusing on Nix-related projects. Though it can also be used outside of Nix projects.
We would like for ndg to be fast, but also flexible and powerful. It boasts several features such as
- Markdown to HTML and Manpage conversion with support for all1 Nixpkgs-flavored Commonmark features.
- Useful Markdown Companions
- Automatic table of contents generation from document headings
- Title Anchors - Automatically generates anchors for headings
- Search functionality to quickly find content across documents (can be
disabled with
--generate-search/-S false
) - Nix module options support to generate documentation from
options.json
- Fully customizable templates to match your project's style fully
- Multi-threading support for fast generation of large documentation sets
- Incredibly fast documentation generation for all scenarios
ndg has several subcommands to handle different documentation tasks:
Usage: ndg [OPTIONS] [COMMAND]
Commands:
init Initialize a new NDG configuration file
export-templates Export default templates to a directory for customization
generate Generate shell completions and manpages for ndg itself
html Generate HTML documentation from markdown files and/or module options
manpage Generate manpage from module options
help Print this message or the help of the given subcommand(s)
Options:
-v, --verbose Enable verbose debug logging
-c, --config <CONFIG_FILE> Path to configuration file (TOML or JSON)
-h, --help Print help
-V, --version Print version
The easiest way to get started with ndg is to use the init
command to generate
a default configuration file:
# Generate a default ndg.toml configuration file
ndg init
# Generate a JSON configuration file instead
ndg init --format json --output ndg.json
# Generate in a specific location
ndg init --output configs/ndg.toml
This will create a well-documented configuration file with all available options and their default values. You can then edit this file to customize your documentation generation process.
The generated configuration includes detailed comments for each option, making it easy to understand what each setting does without needing to refer to the documentation.
Once you have a configuration file, ndg will automatically detect it when run
from the same directory, or you can specify it explicitly with the --config
option:
# Run with automatically detected config
ndg html
# Run with explicitly specified config
ndg html --config path/to/ndg.toml
Tip
One of ndg's most powerful features is its fully customizable template system.
To make customization easier, ndg provides an export-templates
command that
extracts all default templates to your filesystem.
# Export default templates to a 'templates' directory
ndg export-templates
# Export to a custom directory
ndg export-templates --output-dir my-custom-templates
# Overwrite existing files if they exist
ndg export-templates --force
This command exports all template files that ndg uses internally:
default.html
- Main template for documentation pagesoptions.html
- Template for module options pagessearch.html
- Template for the search pageoptions_toc.html
- Template for options table of contentsdefault.css
- Default stylesheetsearch.js
- Search functionality JavaScript
Once exported, you can customize any of these files and use them with the
--template-dir
option:
# Export templates and customize them
ndg export-templates --output-dir my-templates
# Edit the templates as needed
# vim my-templates/default.html
# Use your custom templates
ndg html --template-dir my-templates --input-dir docs --output-dir build
This workflow is designed to aid developers avoid rebuilds, and to make sure users always start with the latest default templates and can easily update them when ndg is upgraded. Though, ndg will fall back to internal templates when none are provided.
The first subcommand in ndg's compartmentalized architecture is for HTML
generation. The html
subcommand includes the following options:
Options:
-i, --input-dir <INPUT_DIR>
Path to the directory containing markdown files
-o, --output-dir <OUTPUT_DIR>
Output directory for generated documentation
-p, --jobs <JOBS>
Number of threads to use for parallel processing
-t, --template <TEMPLATE>
Path to custom template file
--template-dir <TEMPLATE_DIR>
Path to directory containing template files (default.html, options.html, etc.)
-s, --stylesheet <STYLESHEET>
Path to custom stylesheet
--script <SCRIPT>
Path to custom Javascript file to include. Can be specified multiple times to include multiple script files.
-T, --title <TITLE>
Title of the documentation
-f, --footer <FOOTER>
Footer text for the documentation
-j, --module-options <MODULE_OPTIONS>
Path to a JSON file containing module options
--options-depth <OPTIONS_TOC_DEPTH>
Depth of parent categories in options TOC
--manpage-urls <MANPAGE_URLS>
Path to manpage URL mappings JSON file
--generate-search <GENERATE_SEARCH>
Whether to generate search functionality
--highlight-code <HIGHLIGHT_CODE>
Whether to enable syntax highlighting for code blocks
--revision <REVISION>
GitHub revision for linking to source files
-h, --help
Print help
You must give ndg a input directory containing your markdown files
(--input-dir
), an output directory (--output-dir
) to put the created
files, and a title (--title
) for basic functionality. Alternatively, those
can all be read from a config file, passed to ndg through the --config
option.
For example:
ndg html -i ./ndg-example/docs -o ./ndg-example/html -T "Awesome Nix Project"
where ./ndg-example/docs
contains markdown files that you would like to
convert, and ./ndg-example/html
is the result directory.
The manpage
subcommand allows generating a manpage from your options:
ndg manpage -j ./options.json -o ./man/project.5 -T "My Project" -s 5
The generate
subcommand helps create shell completions and manpages for ndg
itself:
ndg generate -o ./dist
Optionally you may pass an options.json
to also render an options.html
page
for a listing of your module options.
ndg supports various customization options to tailor the output to your needs:
The recommended way to customize templates is to use the export-templates
command to get the default templates, then modify them as needed:
# Export all default templates to a directory
ndg export-templates --output-dir my-templates
# Customize the templates as needed
# Edit my-templates/default.html, my-templates/default.css, etc.
# Use your custom templates
ndg html -i ./docs -o ./html -T "Awesome Project" --template-dir ./my-templates
You can also provide a single template file using the --template
option:
ndg html -i ./docs -o ./html -T "Awesome Project" -t ./my-template.html
When using a template directory, ndg will look for the following files:
default.html
- Main template for documentation pagesoptions.html
- Template for options pagesearch.html
- Template for search pageoptions_toc.html
- Template for options table of contentsdefault.css
- Default stylesheetsearch.js
- Search functionality JavaScript
For any missing files, ndg will fall back to its internal templates.
Custom templates use the Tera templating engine, so your templates should use Tera syntax. Below are the variables that ndg will attempt to replace.
{{ title }}
- Document title{{ content|safe }}
- Main content area (unescaped HTML){{ toc|safe }}
- Table of contents (unescaped HTML){{ doc_nav|safe }}
- Navigation links to other documents (unescaped HTML){{ has_options|safe }}
- Conditional display for options page link{{ footer_text }}
- Footer text specified via the --footer option{{ generate_search }}
- Boolean indicating whether search functionality is enabled{{ stylesheet_path }}
- Path to the stylesheet (automatically adjusted for subdirectories){{ main_js_path }}
- Path to the main JavaScript file (automatically adjusted for subdirectories){{ search_js_path }}
- Path to the search JavaScript file (automatically adjusted for subdirectories){{ index_path }}
- Path to the index page (automatically adjusted for subdirectories){{ options_path }}
- Path to the options page (automatically adjusted for subdirectories){{ search_path }}
- Path to the search page (automatically adjusted for subdirectories){{ custom_scripts|safe }}
- Custom script tags (unescaped HTML, automatically adjusted for subdirectories)
Each template can use these variables as needed. For example, in the
default.html
template:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ title }}</title>
<link rel="stylesheet" href="{{ stylesheet_path }}">
<script defer src="{{ main_js_path }}"></script>
{% if generate_search %}
<script defer src="{{ search_js_path }}"></script>
{% endif %}
</head>
<body>
<header>
<h1><a href="{{ index_path }}">{{ title }}</a></h1>
{% if generate_search %}
<div class="search-container">
<input
type="text"
id="search-input"
placeholder="Search documentation..."
>
<a href="{{ search_path }}" class="search-button">Advanced Search</a>
</div>
{% endif %}
</header>
<div class="container">
<nav class="sidebar">
{{ toc|safe }} {{ doc_nav|safe }} {% if has_options %}
<div class="options-link">
<a href="{{ options_path }}">Module Options</a>
</div>
{% endif %}
</nav>
<main class="content">
{{ content|safe }}
</main>
</div>
<footer>
<p>{{ footer_text }}</p>
</footer>
{{ custom_scripts|safe }}
</body>
</html>
You can provide your own CSS stylesheet using the --stylesheet
option:
ndg html -i ./docs -o ./html -T "My Project" -s ./my-styles.css
ndg is able to compile SCSS as well, should you choose to pass it a file with
the .scss
extension. The stylesheet will be compiled in runtime, and placed as
a .css
file in the output directory.
To include a page listing NixOS options, provide a path to your options.json
file:
ndg html -i ./docs -o ./html -T "My Project" -j ./options.json
The options.json
should be in the standard NixOS format. An example from
nvf, created via pkgs.nixosOptionsDoc
.
{
"vim.withRuby": {
"declarations": [
{
"name": "<nvf/modules/wrapper/environment/options.nix>",
"url": "https://github.com/NotAShelf/nvf/blob/main/modules/wrapper/environment/options.nix"
}
],
"default": {
"_type": "literalExpression",
"text": "true"
},
"description": "Whether to enable Ruby support in the Neovim wrapper.\n.",
"example": {
"_type": "literalExpression",
"text": "true"
},
"loc": ["vim", "withRuby"],
"readOnly": false,
"type": "boolean"
}
}
By default, ndg generates a search page and a search widget for your documentation. You can disable this with:
ndg html -i ./docs -o ./html -T "My Project" --generate-search false
This will prevent the creation of search.html and the search index data, and the search widget won't appear in the navigation.
ndg can be integrated into Nix builds to generate documentation for your projects. Here are several ways to use ndg with Nix:
The simplest way to install ndg is directly from its flake:
# Install to your profile
nix profile install github:feel-co/ndg
# Or run without installing
nix run github:feel-co/ndg -- html -i ./docs -o ./result -T "My Project"
The recommended way to generate documentation in Nix builds is to use the
included ndg-builder
function, which properly handles all the complexities:
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
ndg.url = "github:feel-co/ndg";
};
outputs = { self, nixpkgs, ndg, ... }:
let
system = "x86_64-linux";
pkgs = nixpkgs.legacyPackages.${system};
in {
packages.${system}.docs = ndg.packages.${system}.ndg-builder.override {
title = "My Project Documentation";
inputDir = ./docs;
stylesheet = ./assets/style.css;
scripts = [ ./assets/custom.js ];
# For module options
rawModules = [
# From a path
./modules/options.nix
# From options
({
options.example.option = pkgs.lib.mkOption {
type = pkgs.lib.types.str;
default = "value";
description = "An example option";
};
})
];
# Optional: set the depth of options TOC
optionsDepth = 3;
};
};
}
If you need more control, you can use runCommandLocal
directly:
let
pkgs = import <nixpkgs> { };
# Generate options JSON properly
optionsJSON = (pkgs.nixosOptionsDoc {
options = (pkgs.lib.evalModules {
modules = [ ./modules/options.nix ];
}).options;
}).optionsJSON;
# Generate documentation
docs = pkgs.runCommandLocal "project-docs" {
nativeBuildInputs = [ pkgs.ndg ];
} ''
mkdir -p $out
ndg html \
--input-dir ${./docs} \
--output-dir $out \
--title "My Project Documentation" \
--module-options ${optionsJSON}/share/doc/nixos/options.json \
--jobs $NIX_BUILD_CORES
'';
in
docs
This approach correctly handles the generation of options documentation using
nixosOptionsDoc
and properly passes the result to ndg.
-
Use multi-threading for large documentation sets:
ndg html -i ./docs -o ./html -T "My Project" -p $(nproc)
-
Selectively disable features you don't need:
# Disable search if you don't need it ndg html -i ./docs -o ./html -T "My Project" --generate-search false # Disable syntax highlighting for better performance ndg html -i ./docs -o ./html -T "My Project" --highlight-code false
-
Use SCSS for complex stylesheets: ndg automatically compiles SCSS files, allowing you to use variables, nesting, and mixins.
-
Start with exported templates: Always begin customization by exporting the default templates:
# Export to get the latest defaults ndg export-templates --output-dir my-custom-templates
-
Customize specific pages: When using a template directory, you can have different templates for different page types:
templates/ default.html # For regular markdown pages options.html # For the options page search.html # For the search page
-
Use consistent header levels: ndg generates the table of contents based on heading levels. Start with
h1
(#
) for the title, then useh2
(##
) for major sections. -
Structure your options with depth in mind: The
--options-depth
parameter controls how the options TOC is generated. Organize your options to create a logical hierarchy. -
Create an index.md file: Place an
index.md
file in your input directory to serve as the landing page.
-
Link to source with revision: Use the
--revision
option to specify a GitHub revision for source links:ndg html -i ./docs -o ./html -T "My Project" --revision "main"
-
Create per-project config files: For projects with complex documentation, create a dedicated
ndg.toml
file:input_dir = "docs" output_dir = "site" title = "Project Documentation" footer = "© 2025 My Organization" jobs = 8 generate_search = true highlight_code = true options_toc_depth = 2 stylesheet = "assets/custom.scss" script = ["assets/main.js", "assets/search-enhancer.js"] module_options = "generated/options.json"
-
Generate shell completions once and save them to your shell configuration:
# Generate and install completions mkdir -p ~/.local/share/bash-completion/completions ndg generate -o /tmp/ndg-gen cp /tmp/ndg-gen/completions/ndg.bash ~/.local/share/bash-completion/completions/ndg
-
Use admonitions for important content:
<!-- Admonitions are flexible. --> ::: {.warning} This is a critical warning that users should pay attention to! ::: <!-- Make it inline--> ::: {.tip} Here's a helpful tip to make your life easier. :::
-
Create custom ID anchors for important sections:
## Installation {#installation} Refer to the [installation instructions](#installation) above.
ndg is written in Rust, and a derivation is available.
nix build .#ndg -Lv
Alternatively, it can be built inside the dev shell using direnv or
nix develop.
Enter a shell, and cargo build
.
ndg is available under Mozilla Public License, version 2.0. Please see the LICENSE file for more details.
Footnotes
-
This is a best-effort. Regular commonmark is fully supported, but Nixpkgs additions may sometimes break due to the lack of a clear specification. Please open an issue if this is the case for you. ↩