md-formatter

The md-formatter ("mad formatter") is a fast, opinionated Markdown formatter written in Rust.
Why/Approach
Many now use modern tools for linting and formatting node code (biome, oxlint, etc), but these do not support formatting markdown. This tools is meant to provide a rust-based formatter for markdown only. The approach it takes is to parse the markdown with Rust's pulldown-cmark, then pump it back out with opinionated formatting. It explicitly ignore code blocks (for now), it does not mess with resizing tables, and otherwise is pretty rudimentary.
Quick Start
Fast - Formats 360KB of Markdown in 4ms (~90MB/s)
Opinionated - Minimal configuration (--width, --wrap, --ordered-list)
Idempotent - format(format(x)) == format(x) guaranteed
Safe - Uses hard breaks to preserve structure across re-parsing
Complete - All CommonMark + GFM elements supported
Installation
Rust (CLI)
# Install from crates.io
# Or build from source
Node.js
# npm
# pnpm
# bun
Usage
CLI (Rust)
# Format all markdown files in current directory (prints to stdout)
# Format all markdown files in-place
# Check if all files are formatted (for CI)
# Format a specific file
# Format multiple files or directories
# Custom line width
# Read from stdin
|
Glob Patterns
# Use glob patterns
# Format files in a specific directory
# Multiple paths
Exclusions
By default, mdfmt excludes common directories: node_modules, target, .git, vendor, dist, build.
# Add additional exclusions
# Include everything (no default exclusions)
Prose Wrapping
Control how prose (paragraph text) is wrapped with the --wrap option:
# Reflow prose to fit line width (default: 80)
# Unwrap prose into single lines per paragraph
# Keep existing line breaks (default)
| Mode | Description |
|---|---|
always |
Reflow text to fit within line width |
never |
Unwrap each paragraph to a single long line |
preserve |
Leave existing line breaks unchanged (default) |
Ordered Lists
Control how ordered list items are numbered with the --ordered-list option:
# Renumber items sequentially (default)
# Use 1. for all items
| Mode | Description |
|---|---|
ascending |
Renumber items sequentially: 1, 2, 3, ... (default) |
one |
Use 1. for all items |
Integration
# Pre-commit hook
# CI pipeline
||
# Format only changed files
|
Node.js Integration
The npm package includes the mdfmt binary, making it easy to add markdown formatting to your existing Node.js toolchain alongside Biome, ESLint, or other tools.
package.json Scripts
CI Example (GitHub Actions)
- name: Check formatting
run: |
pnpm biome format .
pnpm mdfmt . --check
With Husky/lint-staged
Programmatic API
For advanced use cases, you can also use the formatter programmatically:
import from '@rewdy/md-formatter';
// Format a string
const formatted = ;
// Check if formatted (returns boolean)
const isFormatted = ;
Formatting Rules
Supported Elements
- Paragraphs (line breaks controlled by
--wrapmode) - Headings (normalized to
# Headingformat) - Lists (unordered
-, ordered with--ordered-listmode, with nesting) - Blockquotes (with
>prefix per depth) - Code blocks (fenced, language tags preserved)
- Inline code, emphasis, links
- Horizontal rules (normalized to
---) - Frontmatter (YAML blocks preserved)
- GFM strikethrough and autolinks
Design Philosophy
Uses hard breaks (two spaces + newline) instead of soft breaks to ensure
idempotence. This prevents Markdown parsers from reinterpreting wrapped lines as
soft breaks on re-parsing.
Performance
| Scenario | Time | Throughput |
|---|---|---|
| 360KB file | 4ms | ~90MB/s |
| Average file (2KB) | <1ms | Instant |
Architecture
Input Markdown
↓
Extract Frontmatter (if present)
↓
Parse to Event Stream (pulldown-cmark)
↓
Format Events (state machine with hard breaks)
↓
Prepend Frontmatter
↓
Output Markdown
The formatter never parses the output, so idempotence is guaranteed by design.
CLI Options
)
)
Default exclusions: node_modules, target, .git, vendor, dist, build
Testing
# Run all tests
# Run specific test
# Build release binary
Current status: 25 unit tests passing ✓
Known Limitations
- Autolinks - Converted to regular links (parser limitation)
- Configuration - Only
--width,--wrap, and--ordered-listoptions supported (by design) - MDX - Not supported (different language)
Project Structure
)
)
)
Status
Version: 0.1.1
MVP: Complete ✓
Tests: 25/25 passing ✓
Idempotence: Verified ✓
Performance: Excellent ✓
See STATUS.md for detailed feature matrix and quality metrics.
Contributing
As long as changes are conceptually in-line with the project, I welcome all contributions. 😄
License
MIT