# CLI Reference

Writedocs is meant to be installed globally (`npm install -g writedocs`, see [Quickstart](/docs/quickstart/)) — install it once, then run `writedocs` directly from inside any project's own folder. No local `package.json` dependency, no per-project install step.

Every command takes an optional `[dir]` argument — the content directory containing `writedocs.json` and `docs/`. It defaults to the current directory, so you can `cd` into your docs project and run these with no arguments, or point them at a project elsewhere without `cd`-ing first:

```bash
writedocs dev              # serves the current directory
writedocs dev ./my-docs    # serves ./my-docs instead
```

<Callout type="note">
  Skipped the global install? Every command here also works prefixed with `npx` instead — `npx writedocs dev` — which downloads and runs Writedocs on the fly.
</Callout>

<Callout type="note">
  There's no `writedocs build` here — building the static site and deploying it isn't a step you run yourself. `dev`, `validate`, `broken-links`, `a11y`, `init`, and `update` cover everything you need day to day.
</Callout>

## `writedocs dev`

Starts a local preview of your site and opens it in your browser. Edit any page and the preview updates.

```bash
writedocs dev [dir]
```

```text
✓ Preview ready in 3.9s

  Local:  http://localhost:4321/

  Edit any page and the preview updates. Press Ctrl+C to stop.
```

While it runs, it tells you about problems as you hit them, with the file and line:

```text
✗ docs/guides/setup.mdx:24:1  Expected a closing tag for `<Card>`
⚠ docs/guides/setup.mdx:14  Unknown component <CustomBanner> - showing only its content. Remove it, or define it in a snippet.
⚠ /docs/guides/old-page/  No page at this address (404).
```

| Flag | Description |
|---|---|
| `-p, --port <port>` | Port to run the preview on. Default: 4321. If it's in use, the next free port is used, and the output says so. |
| `--verbose` | Also print the output of the tools writedocs runs underneath. Useful when reporting a bug. |
| `--no-open` | Don't open the preview in the browser. It also stays closed with `BROWSER=none` set, in CI, and when the output isn't a terminal. |

```bash
writedocs dev --port 3000
```

One preview runs at a time. Starting a second one - even for a different project - stops with a message saying where the first is running; stop it with Ctrl+C first.

## `writedocs validate`

Checks `writedocs.json` and every page, and lists every problem it finds in one run - before a build would stop at the first one.

```bash
writedocs validate [dir]
```

**Errors** - the build would fail. `validate` exits with code 1:

- `writedocs.json` doesn't match its schema, or isn't valid JSON.
- A page's frontmatter isn't valid YAML, or doesn't match what a page accepts (for example, a missing `title` or an unknown `mode`).
- A `.mdx` page has an MDX syntax error, like an unclosed component.
- A Markdown image with a relative path (`![Diagram](./diagram.png)`) names a file that doesn't exist.
- The navigation lists a page that doesn't exist.
- A redirect uses a pattern (`/old/:slug`, `/old/*`) - writedocs redirects match one exact path.
- An OpenAPI group's spec (`openapi.src`) doesn't exist, is a URL instead of a file in the project, or doesn't parse - or two OpenAPI groups use the same `openapi.path`.

**Warnings** - the site still builds, but not exactly as written. `validate` exits with code 0:

- A component writedocs doesn't have, like a custom component from another docs tool. The build shows only the content inside it.
- An icon name that isn't in any installed icon set - in a component's `icon`, a page's `icon`, or `writedocs.json`. The build leaves it out.
- A built-in component inside a page component that runs as React (one that uses React hooks, or is passed to a React snippet). It renders simplified there - see [Snippets](/docs/content/snippets/#components-defined-in-a-page).
- An unknown top-level key in `writedocs.json`.
- A page's `openapi` frontmatter names an operation no spec has (`GET /pet` instead of `GET /pets`), or a spec that doesn't exist or doesn't parse. The page shows a notice instead of the API playground.
- An OpenAPI spec that parses but isn't valid OpenAPI. The build uses it as it is, so its API pages may be incomplete.

Each problem names the file and line, and what to do about it:

```text
⚠ 1 warning in the pages

  docs/guides/setup.mdx:14
    Unknown component <CustomBanner> - the build shows only its content.
    Remove it, use a writedocs component instead, or define it in a snippet.
```

| Flag | Description |
|---|---|
| `--config-only` | Check `writedocs.json` only, not the pages. |

Because the exit code is 1 only for errors, `writedocs validate` works as a CI step: it fails on anything that would break the build, and reports the rest.

## `writedocs broken-links`

Checks every link in your pages and `writedocs.json` against the site the build would produce, without building it.

```bash
writedocs broken-links [dir]
```

A link is broken when:

- There's no page at its address - including a page that moved because of a frontmatter `slug`, or a typo.
- Its `#anchor` isn't on the target page. Anchors are headings, the titles of callouts, accordions and updates, and elements with an `id`.
- It links to a file that isn't published. Files in `public/` are published; an image elsewhere in the project is published only where a page displays it, not for a plain link to it.
- It links to a page's source file (`./setup.mdx`) instead of its URL.

Relative links resolve the way a browser resolves them, from the page's URL. Page URLs end in a slash (`/docs/guides/setup/`), so `[Install](install)` on that page points to `/docs/guides/setup/install/` - the output says so, and what to write instead.

```text
✗ 2 broken links

  docs/guides/setup.mdx:12
    install - relative links resolve from the page's own URL (/docs/guides/setup/), so this one points to /docs/guides/setup/install/, which doesn't exist.
    Write /docs/guides/install/ instead.

  docs/guides/setup.mdx:30
    #configuraton - this page has no heading or anchor "#configuraton".
    Did you mean #configuration?

✗ 2 broken links - checked 214 links in 31 pages (12 external links not checked)
```

Links to other sites (`https://`, `mailto:`) aren't checked. The command exits with code 1 when it finds a broken link, so it works as a CI step next to `writedocs validate`.

## `writedocs a11y`

Checks for accessibility problems you can fix in your pages and `writedocs.json`.

```bash
writedocs a11y [dir]
```

- **Color contrast** of the colors `writedocs.json` sets, against WCAG AA (4.5:1 for text): links (`styles.colors.primary`) on the light and dark backgrounds, body text (`styles.colors.text`), white text on the primary color (step numbers, the info banner), and the navbar's text on `styles.navbar`. Dark mode uses the same primary color unless you set `styles.colors.dark.primary` - a dark blue that works on white often doesn't on the dark background.
- **Images without alt text** - `![](/diagram.png)`, or an `<img>` or `<Image>` with no `alt`. For a purely decorative image, write `alt=""`.
- **An `<iframe>` without a `title`.**
- **A link with no text**, which a screen reader reads out as its address.
- **Headings that skip a level** - the page title is the page's `h1`, so sections start at `##`, and `##` followed by `####` skips one. A `#` heading in the page is a second `h1`. Pages with `mode: custom` or `mode: blank` have no title heading, so their own `#` heading is expected.

```text
✗ 2 accessibility issues

  writedocs.json:10
    Links are hard to read in dark mode: the primary color #0029F5 on the dark background #0b1120 has a contrast of 2.36:1 (needs 4.5:1).
    Set a lighter styles.colors.dark.primary for dark mode.

  docs/guides/setup.mdx:18
    Heading level skips from h2 to h4 ("Options").
    Use ### here, or add the missing level above it.

✗ 2 accessibility issues - checked writedocs.json and 31 pages
```

Like `broken-links`, it exits with code 1 when it finds something, so it can run in CI.

## `writedocs convert`

Converts another docs tool's configuration into `writedocs.json` - a Mintlify project, or a project from the previous writedocs:

```bash
writedocs convert --mintlify [dir]
writedocs convert --writedocs [dir]
```

With `--mintlify`, it reads `docs.json` from the project folder (following any `$ref` files it points to); with `--writedocs`, `config.json`. It writes `writedocs.json` next to it. Your pages stay where they are.

Then it prints two lists:

- **What couldn't be carried over as-is**, with the field each item came from - settings writedocs doesn't have, navigation that had to change shape (anchors become tabs, for example), and anything you need to do by hand, like downloading an OpenAPI spec that was a URL.
- **What `writedocs validate` finds in your pages** with the new `writedocs.json` - errors to fix before the first build, and warnings.

| Flag | Description |
|---|---|
| `--mintlify` | Convert a Mintlify project's `docs.json`. `--docs.json` does the same. |
| `--writedocs` | Convert the previous writedocs' `config.json`. `--config.json` does the same. |
| `--force` | Overwrite an existing `writedocs.json`. Without it, `convert` stops rather than replace one. |
| `--dry-run` | Print the converted `writedocs.json` and both lists, without writing anything. |

An older Mintlify project with `mint.json` instead of `docs.json` needs `npx mint upgrade` first. See [Migrating from Mintlify](/docs/migrating-from-mintlify/) and [Migrating from the previous writedocs](/docs/migrating-from-writedocs-v1/) for the full picture.

## `writedocs init`

Scaffolds `writedocs.json` and a starter `docs/` folder.

```bash
writedocs init [dir]
```

Creates:

- `writedocs.json` — a minimal config with one navigation group.
- `index.mdx` — a welcome page, at the project root.
- `docs/getting-started.mdx` — a short getting-started guide.

Existing files are never overwritten — `init` skips (and prints a message for) any file that already exists, so it's safe to run again in a project you've already started customizing.

## `writedocs update`

Updates writedocs to the latest version.

```bash
writedocs update
```

It updates writedocs the way you installed it: a global install with `npm install -g`, or - when writedocs is a dependency of your project - in that project, with npm, pnpm or yarn (whichever lockfile the project has). If you run writedocs through `npx`, it tells you how to run the latest version instead.

When a newer version is out, every command says so after its output:

```text
ℹ writedocs 0.8.0 is available (you have 0.7.1). Run writedocs update to update.
```

The check never slows a command down: writedocs asks npm at most once a day, in the background, and shows the answer on the next run. It's off in CI and when the output isn't a terminal. To turn it off, set the environment variable `WRITEDOCS_NO_UPDATE_CHECK=1`.