---
title: How to Create Man Pages for Rust CLI Applications
summary: Generate man pages for Rust CLI apps using the mdbook-man crate or clap's built-in support to output standard Unix help files.
lang: en
slug: how-to-create-man-pages-for-rust-cli-applications
published: 2026-04-17T14:12:14+00:00
updated: 2026-04-25T22:00:13+00:00
canonical: https://www.rustfaq.org/en/how-to-create-man-pages-for-rust-cli-applications/
---

# How to Create Man Pages for Rust CLI Applications



## When `--help` isn't enough

You built a CLI tool. It works. You run it, it does the thing. Then you try to share it with a colleague who insists on reading documentation in `man`. They type `man your-tool` and get `No manual entry for your-tool`. You point them to `--help`, but the output scrolls off the screen before they can read the flags. You need a man page. Not just a text file, but a proper section 1 manual that integrates with the system, supports paging, and feels native to the Unix environment.

Rust compiles to binaries. It does not generate documentation files by default. The gap between your Rust code and a man page is a gap you have to bridge yourself. The ecosystem provides tools to automate this bridge, keeping your documentation in sync with your code so you never have to maintain two sources of truth.

## What a man page actually is

A man page is a structured text document formatted for the `man` pager. It uses a markup language called roff (or troff). The markup is plain text with special codes for bold, italics, and indentation. The `man` command reads the roff file, formats it, and pipes it to a pager like `less`.

Man pages are organized into sections. Section 1 is for user commands. Section 2 is for system calls. Section 3 is for library functions. Section 5 is for file formats. The section number is encoded in the filename. A man page for a command named `my-tool` lives in a file called `my-tool.1`. The `.1` is not an extension. It is part of the name. The `man` command uses the section number to decide which file to load when you type `man my-tool`.

Convention dictates that man pages are compressed. The file on disk is usually `my-tool.1.gz`. The `man` command decompresses the file on the fly. If you ship an uncompressed file, it works, but it wastes space and breaks convention. Compress your man pages.

## Generating from help text with `mdbook-man`

The `mdbook-man` crate provides a binary tool that generates man pages from standard help text. It reads the output of `--help` and parses it into roff format. This approach works with any CLI library that produces standard help text, including `clap`, `argh`, and `pico-args`.

Install the tool globally. You do not add `mdbook-man` to your project dependencies. It is a build-time utility, not a runtime dependency.

```bash
cargo install mdbook-man
```

Generate the man page by piping your CLI's help output into the tool. The command below runs your binary, captures the help text, and writes the roff output to `my-cli.1`.

```bash
cargo run --bin my-cli -- --help | mdbook-man > my-cli.1
```

The tool looks for standard sections in the help text. It identifies `OPTIONS` and assumes flags follow. It identifies `ARGUMENTS` and assumes positional arguments follow. It converts the plain text into roff commands. The result is a valid man page file.

You can view the generated file immediately. The `man` command accepts a path to a file.

```bash
man ./my-cli.1
```

If the output looks correct, compress the file for distribution.

```bash
gzip my-cli.1
```

The file is now `my-cli.1.gz`. This is the artifact you ship.

### Convention aside: Help text stability

`mdbook-man` relies on the structure of your help text. If you change the help format, the parser might break. Stick to standard help output. Do not use custom formatters that deviate from the expected layout. If your CLI library supports a "man page compatible" help mode, use it. `clap` produces standard output by default, so it works well with `mdbook-man`.

## Generating from structure with `clap`

If you use `clap`, you can generate man pages directly from the command definition. This approach is structural. It reads the `Command` object and emits roff without parsing text. This is more robust than text parsing because it does not depend on help formatting.

Enable the `cargo` feature in `clap` if you are using the derive macro, or use the builder API. The `clap` crate includes a `render_man` method on `Command`.

```rust
use clap::{Command, Arg};

fn main() {
    let cmd = Command::new("my-cli")
        .version("0.1.0")
        .about("A demonstration CLI")
        .arg(
            Arg::new("file")
                .help("The input file to process")
                .required(true)
        )
        .arg(
            Arg::new("verbose")
                .short('v')
                .long("verbose")
                .help("Enable verbose output")
                .action(clap::ArgAction::SetTrue)
        );

    // Render the man page to stdout
    clap_mangen::Man::new(cmd.clone())
        .render(&mut std::io::stdout())
        .expect("Failed to render man page");
}
```

The `clap_mangen` crate is the official generator for `clap`. Add it to your dependencies.

```toml
[dependencies]
clap = { version = "4", features = ["derive"] }
clap_mangen = "0.2"
```

Run the binary and redirect the output.

```bash
cargo run --bin my-cli -- --generate-man > my-cli.1
```

This requires a custom subcommand or flag in your code to trigger generation. The example above renders to stdout in `main`, which is not ideal for a real app. In practice, you add a `--generate-man` flag that calls the renderer and exits.

```rust
use clap::{Parser, Command};
use clap_mangen::Man;
use std::io::stdout;

#[derive(Parser)]
#[command(name = "my-cli", version, about = "A demonstration CLI")]
struct Cli {
    #[arg(help = "The input file to process")]
    file: String,

    #[arg(short, long, help = "Enable verbose output")]
    verbose: bool,

    #[arg(long, hide = true, action = clap::ArgAction::SetTrue)]
    generate_man: bool,
}

fn main() {
    let cli = Cli::parse();

    if cli.generate_man {
        let cmd = Cli::command();
        Man::new(cmd)
            .render(&mut stdout())
            .expect("Failed to render man page");
        return;
    }

    // Normal execution
    println!("Processing {}", cli.file);
}
```

Generate the man page by running the flag.

```bash
cargo run --bin my-cli -- --generate-man > my-cli.1
```

The structural approach captures all metadata from your `Command` definition. It includes version numbers, authors, and usage strings automatically. It is the preferred method for `clap` users.

## The installation gap

Generating the man page is only half the work. Users need to install the file in the correct location. The `man` command searches directories listed in the `MANPATH` environment variable. The default path usually includes `/usr/share/man/man1/` and `/usr/local/share/man/man1/`.

`cargo install` does not support man pages. It only installs the binary. You must provide a custom installation step. This is a common pain point for Rust CLI developers. You can solve it with a shell script, a Makefile, or a package manager.

A simple install script copies the binary and the man page to the target directories.

```bash
#!/bin/bash
set -e

PREFIX="${PREFIX:-/usr/local}"
BINDIR="${PREFIX}/bin"
MANDIR="${PREFIX}/share/man/man1"

# Install binary
install -Dm755 target/release/my-cli "$BINDIR/my-cli"

# Install man page
install -Dm644 my-cli.1.gz "$MANDIR/my-cli.1.gz"

echo "Installed my-cli and man page."
```

Users run this script with `sudo` or with a custom `PREFIX`. The `install` command creates directories as needed and sets permissions. The man page ends up in the right place. Users can then run `man my-cli` and see your documentation.

If you distribute via a package manager like Homebrew, Nix, or Debian packages, the package definition handles the man page installation. You just provide the `.1.gz` file as an artifact.

### Pitfall: Section numbers matter

If you name your file `my-cli.5`, it goes to section 5. Users running `man my-cli` might not find it if the system prioritizes section 1. Always use section 1 for user commands. If you have a configuration file format, use section 5 for that. Name it `my-cli.conf.5`. The section number is part of the identity. Get it right.

### Pitfall: `build.rs` limitations

You might want to generate the man page in `build.rs`. This is tricky. The binary does not exist yet when `build.rs` runs. You cannot run the binary to generate help text. You can use `clap_mangen` in `build.rs` if you re-export the `Command` definition, but this requires duplicating the definition or using a shared crate. Most projects generate man pages in a post-build step or in CI, not in `build.rs`. Keep generation separate from compilation.

## Decision: when to use which tool

Use `clap_mangen` when you are using `clap` and want structural generation that derives directly from your command definition without parsing text. Use `mdbook-man` when you use `argh`, `pico-args`, or any CLI library that produces standard help text but lacks a dedicated man page generator. Use manual roff when you need precise control over formatting that generators cannot handle, though this is rare and error-prone. Use a custom install script when you distribute binaries directly and need to place the man page in the system directories. Use a package manager when you target Linux distributions or macOS and want the system to handle installation paths.

Generating the file is half the battle. Shipping it is the other half. Make sure your users can find the man page.

## Where to go next

- [How to build a CLI app](/en/how-to-build-a-cli-app/)
- [How to Package Rust CLI Tools for Multiple Platforms](/en/how-to-package-rust-cli-tools-for-multiple-platforms/)
- [How to Use the assert_cmd and predicates Crates for CLI Testing](/en/how-to-use-the-assertcmd-and-predicates-crates-for-cli-testing/)