Skip to content

Generate Markdown documentation

usage generate markdown renders a spec as Markdown reference pages: one page for the whole CLI, or one per command for a docs site with a sidebar. The CLI reference on this site is its output for usage's own spec.

A single file goes to --out-file, or to stdout when there is none:

sh
usage g markdown -f ./mycli.usage.kdl --out-file ./docs/cli.md
usage g markdown -f ./mycli.usage.kdl > ./docs/cli.md

--multi writes one page per command into --out-dir, nested the way the commands are:

sh
usage generate markdown --file ./mycli.usage.kdl --multi --out-dir ./docs
tree ./docs
text
docs
├── config
│   ├── add.md
│   ├── list.md
│   └── remove.md
├── index.md
└── update.md

Links between the pages are written from the root of the output, as /bash.md. --url-prefix /cli/reference puts a path in front of them, /cli/reference/bash.md, which is what a docs site serving the pages under a subdirectory needs.

Use --link-extension .html when your site serves rendered HTML, or --link-extension '' for extensionless URLs. The default is .md. This affects command and configuration links, while generated files still end in .md. Rust callers can use with_link_extension(".html"); custom templates receive link_extension and config_link alongside the existing url_prefix.

Custom templates from the CLI

Every part of the output comes from a Tera template, and --template NAME=PATH replaces one. A custom single-file document can keep the built-in command template by including it:

sh
usage g markdown -f ./mycli.usage.kdl \
    --template spec=./templates/spec.md.tera \
    --out-file ./docs/cli.md
jinja
{# templates/spec.md.tera #}
# {{ spec.bin }} reference
{% set cmd = spec.cmd %}
{% include "cmd_template.md.tera" %}

The names are spec, index, command, argument, flag, and config. Repeat --template to replace more than one. Templates that are not named keep their built-in definitions and remain available through Tera's include.

Render from Rust

These examples use the usage-lib crate, whose library name is usage. It is separate from the usage-rs framework facade:

toml
[dependencies]
usage = { package = "usage-lib", version = "6" }

MarkdownRenderer in usage-lib bundles the same templates. A Rust caller replaces one member without copying the rest:

rust
use usage::docs::markdown::{MarkdownRenderer, MarkdownTemplate};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let spec: usage::Spec = std::fs::read_to_string("mycli.usage.kdl")?.parse()?;
    let markdown = MarkdownRenderer::new(spec)
        .with_template(
            MarkdownTemplate::Spec,
            "# {{ spec.bin }} reference\n{% set cmd = spec.cmd %}\n{% include \"cmd_template.md.tera\" %}",
        )
        .render_spec()?;
    print!("{markdown}");
    Ok(())
}

The members are Spec, Index, Command, Argument, Flag, and Config. Templates that are not replaced remain available through include; syntax and include errors are returned when the page is rendered, not when the template is set.

Pages use MarkdownTheme::Compact by default: arguments and flags are grouped into dense lists that stay easy to scan on a large page. MarkdownTheme::Detailed gives each entry its own heading, so that a flag can be linked to directly:

rust
use usage::docs::markdown::{MarkdownRenderer, MarkdownTheme};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let spec: usage::Spec = std::fs::read_to_string("mycli.usage.kdl")?.parse()?;
    let markdown = MarkdownRenderer::new(spec)
        .with_theme(MarkdownTheme::Detailed)
        .render_spec()?;
    print!("{markdown}");
    Ok(())
}
MIT LicenseCopyright © 2026jdx.dev