# rumdl: A Rust Markdown Linter and Formatter with markdownlint Compatibility

> rumdl is a single Rust binary that lints and formats Markdown, reads existing markdownlint configuration, and ships 84 rules plus editor and CI integration. Here is what it does well, where it is thin, and what to check before adopting it.

**rvben/rumdl** — Fast Markdown linter and formatter written in Rust

- Repository: https://github.com/rvben/rumdl
- Stars: 1,546 · Forks: 87
- Language: Rust
- License: MIT
- Published: 2026-09-10 · Updated: 2026-09-10 · Language: en
- Canonical page: https://hysenlabs.com/projects/rvben-rumdl

## The gap rumdl targets: markdownlint speed and one binary

Markdown linting in most repositories means markdownlint-cli2 or a pre-commit hook that shells out to Node. That works, but it puts a JavaScript runtime between the author and the feedback, and it splits linting from formatting: you lint with one tool and often format with another. rumdl's README frames the project as inspired by Ruff, and the parallel is deliberate. Ruff took a Python linter and made it a single fast binary; rumdl does the same for Markdown, bundling linting, formatting, caching and editor integration into one executable.

The intended audience is narrow and identifiable. It is for maintainers who already have a markdownlint configuration and do not want to rewrite it, for documentation repositories where Markdown is the product rather than a side file, and for anyone who wants formatting and linting to be the same invocation. The README lists Firefox, Docker Docs, Apache Lucene, Carbon, Crystal, PyO3 and Rustlings as adopters, which tells you the target is large documentation trees, not a single README.

The claim worth scrutinising is speed. The README says rumdl is "designed for speed" and links to a benchmark page at rumdl.dev with dated Rust Book results, scope and limitations. The repository itself does not carry the numbers, so treat the benchmark as something to read on its own terms, including the stated limitations, rather than as a settled result.

## How rumdl works: rules, flavors, caching and the LSP server

rumdl parses Markdown, runs a rule set over the resulting structure, and reports violations with locations. The README states there are 84 lint rules, which the README injects into the text via a RULE_COUNT marker, so the count is generated from the rule registry rather than typed by hand. Rules are individually addressable: the CLI has a rule subcommand that takes an optional rule identifier, and rules.json plus rumdl.schema.json sit at the repository root, which is how the project keeps the rule list and the configuration schema in sync for editors.

Configuration is TOML. rumdl discovers common markdownlint and markdownlint-cli2 configuration files automatically, so an existing project can run rumdl first and compare output before changing anything. Beyond the global settings there is a flavor concept: the README lists GFM, MkDocs, MDX, Quarto and MyST, with auto-detection for supported file names. That matters because MDX and MyST are not plain CommonMark, and a linter that assumes CommonMark will flag syntax that is valid in those dialects.

Caching is the other structural piece. The README says rumdl only re-lints files that have changed, which is what makes watch mode and editor integration practical. The repository layout backs this up: there is a server subcommand and a dedicated LSP section, and the library is exposed as rumdl_lib with both cdylib and rlib crate types, so the same rule engine serves the CLI, the language server and the WebAssembly build implied by the wasm-pkg directory.

The formatter is a separate command, fmt, and the exit-code semantics differ from check. The README is explicit that fmt exits 0 on success even if unfixable violations remain, while check --fix exits 0 only if everything was fixed and 1 if violations remain. That distinction is easy to miss when wiring CI, and it is the kind of detail that decides whether a pipeline fails on a real problem or passes silently.

## Installing rumdl and running a first check

The README gives several installation routes: Homebrew, Cargo, npm, pip, uv, mise, Nix, the Termux User Repository, pacman, or a downloaded binary. If you want to evaluate rumdl without installing anything, the quick start uses uvx to run it once. The command below runs a check over the current directory and prints violations with file and line information.

```bash
uvx rumdl check .
```

After a package-manager install, the same check is a direct invocation. The binary has no runtime requirements according to the README, so this is the whole setup.

```bash
rumdl check .
```

If you already use markdownlint, the README advises keeping your existing configuration for the first run rather than migrating immediately. rumdl discovers markdownlint and markdownlint-cli2 config files automatically, so the first check should produce output comparable to what you already get. To generate a fresh TOML configuration instead, the init command writes a default config file.

```bash
rumdl init
```

Once the config exists, the two commands you will use most are formatting and fix. Note the exit-code difference described above: fmt returns 0 even when unfixable violations remain, whereas check --fix returns 1 when anything is left unfixed.

```bash
rumdl fmt .
rumdl check --fix .
```

To see which settings are actually in effect rather than which are inherited from defaults, the README documents rumdl config with two filters. This is the fastest way to confirm that your markdownlint-compatible settings were picked up.

```bash
rumdl config --no-defaults
```

## Where rumdl is the wrong tool, and the formatting risk

The clearest limitation is the formatter. rumdl fmt rewrites files, and the README does not document a rollback mechanism, a dry-run mode for fmt, or a diff preview. That is not a fatal gap, but it means the safe path is version control plus a reviewable diff, and it means fmt is a poor fit for generated Markdown or files you do not own. The exit-code design compounds this: fmt exits 0 even when unfixable violations remain, so a CI job that runs only fmt will pass on a file that still violates rules. If you want CI to fail on violations, check --fix is the command whose exit code actually reflects the outcome.

Second, the project is at 0.x. The pyproject.toml classifies it as Development Status 4 - Beta, and the release history shows patch releases landing every few days in early September 2026, with the last push on 2026-09-10. That cadence is a signal about how much is still moving. The README has a Stability section, but the version numbers alone tell you not to expect interface guarantees across minor releases.

Third, the Markdown flavor support is a trade-off. Auto-detection for GFM, MkDocs, MDX, Quarto and MyST is convenient until it guesses wrong, and a linter that misidentifies the dialect will produce confident but incorrect violations. For a repository that mixes dialects, or uses a flavor rumdl does not list, you will spend time in configuration rather than in linting.

Finally, rumdl is a Markdown tool. If your documentation lives in reStructuredText, AsciiDoc or a docs-as-code system with its own linter, rumdl has nothing to say about it.

## rumdl versus markdownlint-cli2 and prettier

The honest comparison is with markdownlint-cli2, because rumdl deliberately targets its configuration format. The difference in approach is runtime and scope. markdownlint-cli2 runs on Node and lints; formatting is usually delegated to prettier or handled manually. rumdl is a single static binary with no runtime, and it lints and formats from the same rule engine. If your CI already has Node and your formatting is settled, markdownlint-cli2 is the lower-risk choice: it is the reference implementation, its behavior is what your existing config was written against, and you are not betting on a 0.x project's compatibility layer. If your CI is polyglot, or you want lint and format to agree by construction, rumdl's single-binary model is the argument.

Prettier is a different axis. It is a formatter with an opinionated style and limited linting, and the repository ships examples/prettier-compatible.rumdl.toml, which signals that rumdl can be configured to produce output closer to prettier's style. That is a migration aid, not equivalence: the example file exists precisely because the two tools do not agree by default.

There is also a Python packaging angle. rumdl builds through maturin with a binary binding and requires Python 3.9 or later, so pip and uv installs ship the compiled binary rather than a Python reimplementation. That is a genuine convenience for Python-centric repositories, and it is something markdownlint-cli2 does not offer.

## Maintenance, licensing and the upgrade cost you should price in

The repository is not archived, and the last push was on 2026-09-10. The release cadence around that date was three releases in four days (v0.2.68, v0.2.69, v0.2.70), while Cargo.toml in the repository lists version 0.2.73, which indicates the working tree runs ahead of the published release list. That is normal for an active project, but it means the version you install from a package manager and the version in the repository are not the same thing, and you should check which one you are reporting bugs against.

The upgrade cost is mostly configuration drift. Because rumdl reads markdownlint configuration, an upgrade can change how an existing setting is interpreted, and the effective configuration is the thing to inspect after each bump. rumdl config --no-defaults prints only the settings that differ from defaults, which is the cheapest way to spot a change. For teams pinning versions, the Cargo.toml rust-version field is 1.94.0 and the edition is 2024, so building from source requires a recent toolchain; the prebuilt binaries and package-manager installs avoid that entirely.

Licensing is MIT, stated in both the Cargo.toml and pyproject.toml metadata and shown in the README badge. MIT is permissive and imposes no copyleft obligation on your own code. The project also accepts sponsorship through GitHub Sponsors and runs a Discord server, neither of which affects the licence. This is a description of the licence file, not legal advice; if your organization has specific requirements around attribution or bundled binaries, have counsel read the LICENSE file.

## Conclusion

Adopt rumdl if you already run markdownlint or markdownlint-cli2 and want the same rule set from one native binary with caching and an LSP mode, and if you can accept a 0.x tool whose CLI defaults are still shifting between patch releases. Do not adopt it if you need a frozen, long-term-stable interface or a documented rollback path for its formatter, because the README does not describe one. Before committing, run rumdl check . against your existing markdownlint configuration, then rumdl config --no-defaults to see exactly which settings rumdl is actually applying rather than inheriting.

## FAQ

### How do I install rumdl?

The README lists Homebrew, Cargo, npm, pip, uv, mise, Nix, the Termux User Repository, pacman, and standalone binary downloads. For a one-off run without installing, the quick start uses uvx rumdl check .

### Can rumdl read my existing markdownlint configuration?

Yes. The README states that rumdl discovers common markdownlint and markdownlint-cli2 configuration files automatically, and it advises keeping your existing configuration for the first run so you can compare results before changing your workflow.

### What is the difference between rumdl fmt and rumdl check --fix?

The README states that fmt exits 0 on success even if unfixable violations remain, while check --fix exits 0 if all violations were fixed and 1 if any remain. For CI that should fail on remaining violations, check --fix is the command whose exit code reflects that.

### Which Markdown flavors does rumdl support?

The README lists GFM, MkDocs, MDX, Quarto and MyST, with auto-detection for supported file names. Configuration for flavors is documented separately from the general settings.

### How many lint rules does rumdl have?

The README states 84 lint rules, and the count is injected into the README from a RULE_COUNT marker rather than written by hand. The rule list is also published as rules.json at the repository root.

### Does rumdl have an LSP server for editor integration?

Yes. The README documents a server subcommand and a dedicated LSP section, and the repository exposes the engine as rumdl_lib with cdylib and rlib crate types so the same rules serve the CLI and the language server.

## Sources

- [Issues](https://github.com/rvben/rumdl/issues)
- [License: MIT](https://github.com/rvben/rumdl/blob/main/LICENSE)
- [README](https://github.com/rvben/rumdl/blob/main/README.md)
- [Releases](https://github.com/rvben/rumdl/releases)
- [rvben/rumdl on GitHub](https://github.com/rvben/rumdl)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/rvben-rumdl
