Opinionated Markdown formatter and linter
Project description
mdlint
An opinionated Markdown formatter and linter, written in Rust.
What ruff did for Python and gofmt did for Go,
mdlint aims to do for Markdown: enforce a single, consistent canonical style so that style debates disappear and diffs
stay meaningful. As AI coding agents increasingly read and write Markdown, well-structured files matter more than ever.
Run mdlint format and stop thinking about it.
Project Status: Active development, in between my day job.
Features
- Formatter first:
mdlint formatrewrites files to a canonical style — no configuration required - Linter second:
mdlint checkreports violations; most are auto-fixable by the formatter - Fast: written in Rust for performance
- Portable: single, small, 0-dependency binary (Linux x86_64/ARM64, macOS Intel/Apple Silicon, Windows)
- Git-aware: respects
.gitignorefiles by default
Installation
pip install markdownlint-rs
Or with uv:
uv tool install markdownlint-rs
How it works
pip install markdownlint-rs downloads a platform-specific wheel that bundles the correct pre-built mdlint binary
for your OS and architecture. The mdlint command is a thin Python wrapper that locates and execs that binary.
No Rust toolchain is required.
| Platform | Architecture | Wheel tag |
|---|---|---|
| Linux (glibc) | x86_64 | manylinux_2_17_x86_64 |
| Linux (glibc) | aarch64 | manylinux_2_17_aarch64 |
| Linux (musl) | x86_64 | musllinux_1_2_x86_64 |
| Linux (musl) | aarch64 | musllinux_1_2_aarch64 |
| macOS | x86_64 | macosx_10_12_x86_64 |
| macOS | arm64 (Apple Silicon) | macosx_11_0_arm64 |
| Windows | x86_64 | win_amd64 |
Usage
Basic Usage
Check Markdown files for issues:
# check all Markdown files (auto-detected)
mdlint check
# check specific files or directories
mdlint check README.md docs/
# check and apply auto-fixes
mdlint check --fix
Format Markdown files (opinionated, fixes everything):
# format all Markdown files
mdlint format
# verify formatting without modifying files
mdlint format --check
# format specific files or directories
mdlint format README.md docs/
Command-Line Options
mdlint check
Lint Markdown files and report issues.
Usage: mdlint check [OPTIONS] [FILES]...
Arguments:
[FILES]... Files or directories to check (defaults to current directory)
Options:
--fix Apply auto-fixes where possible
--format <FORMAT> Output format: default or json [default: default]
--exclude <PATH> Exclude files or directories
--config <CONFIG> Path to configuration file
-v, --verbose Print each file name as it is checked
--color <COLOR> Color output: auto, always, never [default: auto]
-h, --help Print help
mdlint format
Format Markdown files with opinionated fixes.
Usage: mdlint format [OPTIONS] [FILES]...
Arguments:
[FILES]... Files or directories to format (defaults to current directory)
Options:
--check Only verify formatting, don't modify files
--exclude <PATH> Exclude files or directories
--config <CONFIG> Path to configuration file
--color <COLOR> Color output: auto, always, never [default: auto]
-h, --help Print help
Examples
Check with auto-fix:
mdlint check --fix
Check with custom config file:
mdlint check --config mdlint.toml
Check with JSON output:
mdlint check --format json
Check specific files:
mdlint check README.md CONTRIBUTING.md docs/
Format all files:
mdlint format
Verify formatting in CI:
mdlint format --check
Disable color output:
mdlint check --color never
Show each file as it is checked:
mdlint check --verbose
Configuration
mdlint uses TOML configuration files, similar to how ruff uses ruff.toml.
The tool automatically discovers configuration files by searching up from the current directory.
Configuration File Locations
The tool searches for these files in order (first found wins per directory level):
mdlint.toml.mdlint.toml
Configuration File Format
Create a mdlint.toml file in your project root:
# Enable all rules by default
default_enabled = true
# Respect .gitignore files when discovering files
gitignore = true
# Disable inline configuration comments
no_inline_config = false
# Rule-specific configuration
[rules.MD013]
line_length = 120
heading_line_length = 80
code_blocks = false
[rules.MD003]
style = "atx"
[rules.MD004]
style = "asterisk"
[rules.MD007]
indent = 2
# Disable specific rules
[rules.MD034]
enabled = false
Configuration Options
See mdlint.default.toml for every option
with its default value and a description of what it does. The global options are summarised below.
Global Options
default_enabled(boolean): Whentrue, all rules are enabled unless explicitly disabled. Default:falsegitignore(boolean): Respect.gitignorefiles when discovering markdown files. Default:trueno_inline_config(boolean): Disable inline configuration via HTML comments. Default:falseexclude(array): Paths to exclude from file discovery; merged with any--excludeCLI flags. Default:[]custom_rules(array): Paths to custom rule modules (future feature). Default:[]front_matter(string): Pattern for front matter detection. Default: auto-detects YAML (---) and TOML (+++)fix(boolean): Whentrue,mdlint checkautomatically applies all auto-fixable violations, equivalent to passing--fixon the command line. Default:true
Rule Configuration
Rules can be configured in three ways:
-
Enable/Disable a rule:
[rules.MD013] enabled = false
-
Configure rule parameters:
[rules.MD013] line_length = 100 code_blocks = false
-
Use both (parameters implicitly enable the rule):
[rules.MD003] style = "atx"
Configuration Hierarchy
Configurations are discovered by walking up the directory tree. When multiple configs are found, they are merged with the following precedence (highest to lowest):
- Command-line options (
--config) - Local directory config (
mdlint.tomlin current dir) - Parent directory configs (walking up to root)
- Default configuration
Later configs override earlier ones for scalar values. When a rule is configured in multiple places, the most specific configuration wins.
See the markdownlint rules documentation for details on each rule and its configuration options.
Inline Configuration
Rules can be suppressed for specific lines using HTML comments, without modifying mdlint.toml:
<!-- mdlint-disable-next-line MD013 -->
This line may be longer than the configured limit.
<!-- mdlint-disable MD033 -->
<div>Raw HTML block that needs to stay as-is</div>
<!-- mdlint-enable MD033 -->
| Comment | Effect |
|---|---|
<!-- mdlint-disable MD001 --> |
Disable rule from this line onward |
<!-- mdlint-enable MD001 --> |
Re-enable rule from this line onward |
<!-- mdlint-disable-next-line MD001 --> |
Disable rule for the next line only |
<!-- mdlint-disable --> |
Disable all rules from this line onward |
<!-- mdlint-enable --> |
Re-enable all rules |
Multiple rules: <!-- mdlint-disable MD001 MD013 --> — space-separate rule codes.
Set no_inline_config = true in mdlint.toml to ignore all inline comments.
Exit Codes
- 0: Success - no linting errors found (or files successfully formatted with
format) - 1: Linting errors found (or formatting issues found with
format --check) - 2: Runtime error (invalid config, file not found, etc.)
Use exit codes in CI/CD pipelines:
# Fail build on linting errors
mdlint check || exit 1
# Fail build if files need formatting
mdlint format --check || exit 1
Supported Rules
mdlint implements the markdownlint rule set. Rules marked
✅ are enforced automatically by mdlint format; rules marked ❌ are reported by mdlint check only.
| Rule | Description | Format fixes |
|---|---|---|
| MD001 | Heading levels should only increment by one level at a time | ❌ |
| MD003 | Heading style | ✅ |
| MD004 | Unordered list style | ✅ |
| MD005 | Inconsistent indentation for list items at the same level | ❌ |
| MD007 | Unordered list indentation | ❌ |
| MD009 | Trailing spaces | ✅ |
| MD010 | Hard tabs | ✅ |
| MD011 | Reversed link syntax | ❌ |
| MD012 | Multiple consecutive blank lines | ✅ |
| MD013 | Line length | ❌ |
| MD018 | No space after hash on atx style heading | ✅ |
| MD019 | Multiple spaces after hash on atx style heading | ✅ |
| MD022 | Headings should be surrounded by blank lines | ✅ |
| MD023 | Headings must start at the beginning of the line | ✅ |
| MD025 | Multiple top-level headings in the same document | ❌ |
| ... | See markdownlint rules | ... |
Pre-commit Hooks
Native git hook
Create .git/hooks/pre-commit (and make it executable with chmod +x):
#!/bin/sh
mdlint format --check
This causes git commit to fail if any staged Markdown file needs formatting.
pre-commit framework
Add to .pre-commit-config.yaml:
repos:
- repo: https://github.com/swanysimon/mdlint
rev: v0.3.16 # use the latest release tag
hooks:
- id: mdlint-format-check
name: mdlint format --check
language: system
entry: mdlint format --check
types: [markdown]
- id: mdlint-check
name: mdlint check
language: system
entry: mdlint check
types: [markdown]
Or use mdlint check --fix to auto-fix and stage the result:
- id: mdlint-fix
name: mdlint check --fix
language: system
entry: mdlint check --fix
types: [markdown]
pass_filenames: false
GitHub Actions
- name: Check Markdown formatting
run: mdlint format --check
- name: Lint Markdown
run: mdlint check
Contributing
Contributions are welcome! See the main repository for development setup, code quality standards, and the pull request process.
Build a wheel locally
cd python
# Pure-Python wheel (no binary bundled — for metadata validation only)
uv build --wheel
# Platform-specific wheel with a binary
cp /path/to/mdlint-binary mdlint/mdlint
MDLINT_PLATFORM_TAG=macosx_11_0_arm64 uv build --wheel
MDLINT_PLATFORM_TAG is read by hatch_build.py to stamp the correct platform tag onto the wheel.
Without it, the wheel is tagged py3-none-any and contains no binary — useful for metadata validation in CI
but not for distribution.
Validate package metadata
cd python
uv build --wheel
uvx twine check dist/*.whl
Platform tags
| Asset | MDLINT_PLATFORM_TAG |
|---|---|
mdlint-linux-x86_64 |
manylinux_2_17_x86_64.manylinux2014_x86_64 |
mdlint-linux-x86_64-musl |
musllinux_1_2_x86_64 |
mdlint-linux-aarch64 |
manylinux_2_17_aarch64.manylinux2014_aarch64 |
mdlint-linux-aarch64-musl |
musllinux_1_2_aarch64 |
mdlint-macos-x86_64 |
macosx_10_12_x86_64 |
mdlint-macos-aarch64 |
macosx_11_0_arm64 |
mdlint-windows-x86_64.exe |
win_amd64 |
Release
Releases are automated via .github/workflows/publish-python.yml. On a version tag push, the workflow downloads
each pre-built binary from the GitHub release, builds a platform-specific wheel, and publishes it to PyPI via
trusted publishing (OIDC, no token required).
License
The Unlicense - see LICENSE for details.
Acknowledgments
- markdownlint by David Anson — original rule definitions
- mdformat — inspiration for the formatter-first approach
- pulldown-cmark — Markdown parsing
Resources
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distributions
Built Distributions
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file markdownlint_rs-0.3.16-py3-none-win_amd64.whl.
File metadata
- Download URL: markdownlint_rs-0.3.16-py3-none-win_amd64.whl
- Upload date:
- Size: 1.7 MB
- Tags: Python 3, Windows x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.15 {"installer":{"name":"uv","version":"0.11.15","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
97d1e83dc3e8521618baf8ebb2b36854f4410a42d612ff2c04df4b9c9899262a
|
|
| MD5 |
d552e20ec74e61920c48bdca8109b58b
|
|
| BLAKE2b-256 |
8a52ba3cfb7b5e2905dcc353f196c308380633a7f6f25c1dddfe9655b58f741d
|
File details
Details for the file markdownlint_rs-0.3.16-py3-none-musllinux_1_2_x86_64.whl.
File metadata
- Download URL: markdownlint_rs-0.3.16-py3-none-musllinux_1_2_x86_64.whl
- Upload date:
- Size: 1.8 MB
- Tags: Python 3, musllinux: musl 1.2+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.15 {"installer":{"name":"uv","version":"0.11.15","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
237b573c70d0c7e4b9a5d596331afe00404f1465fcd0a2791bbb4bccafa08b0a
|
|
| MD5 |
1c0454bffa7a8fbcdba9f2de79b0d2b0
|
|
| BLAKE2b-256 |
f297ae3c79ad6d1c1a0d18ffa65bdfde176d75c98670e55859f0c7faf0655824
|
File details
Details for the file markdownlint_rs-0.3.16-py3-none-musllinux_1_2_aarch64.whl.
File metadata
- Download URL: markdownlint_rs-0.3.16-py3-none-musllinux_1_2_aarch64.whl
- Upload date:
- Size: 1.6 MB
- Tags: Python 3, musllinux: musl 1.2+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.15 {"installer":{"name":"uv","version":"0.11.15","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
95aa18bc7330ca7f5f677841cf7fa68cad610c926c3a6f1c90dcf4e670e9d2ee
|
|
| MD5 |
d5dfca3d40fb6f3d8c5708897fe4eaa0
|
|
| BLAKE2b-256 |
2ff3802d408b0983588143bd522d45a4f7fbe28ab76b90c11354fecd50bc512b
|
File details
Details for the file markdownlint_rs-0.3.16-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.
File metadata
- Download URL: markdownlint_rs-0.3.16-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
- Upload date:
- Size: 1.7 MB
- Tags: Python 3, manylinux: glibc 2.17+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.15 {"installer":{"name":"uv","version":"0.11.15","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
234f570fe61fdf0e7fa496f4c7772d6c752034f56a6afc6d78e69b35ac2a48e4
|
|
| MD5 |
f754cbb74239983762b7b1fa42460c30
|
|
| BLAKE2b-256 |
6a8e0a4e258a88779b5f316f8bbd842dcd3e81e4520fe4a18275acf5fb331b07
|
File details
Details for the file markdownlint_rs-0.3.16-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.
File metadata
- Download URL: markdownlint_rs-0.3.16-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
- Upload date:
- Size: 1.6 MB
- Tags: Python 3, manylinux: glibc 2.17+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.15 {"installer":{"name":"uv","version":"0.11.15","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
04d8d4a69fd9c84ac313fd87c334b7758dc00a01349b83d943b09326cb672a46
|
|
| MD5 |
1f06530d3e399376318aae37daba9753
|
|
| BLAKE2b-256 |
91ec054335199ea2ea2571f31c7a8e72122be0e4979e4b5227a2f7933e89d7c6
|
File details
Details for the file markdownlint_rs-0.3.16-py3-none-macosx_11_0_arm64.whl.
File metadata
- Download URL: markdownlint_rs-0.3.16-py3-none-macosx_11_0_arm64.whl
- Upload date:
- Size: 1.5 MB
- Tags: Python 3, macOS 11.0+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.15 {"installer":{"name":"uv","version":"0.11.15","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d73c3f9824ef767f8f841b273ed2c352d8a423e6c48fb1a25ef536f2755bd7cb
|
|
| MD5 |
9e45de095df08c099ff042126ea51fda
|
|
| BLAKE2b-256 |
99341da9e33497a69c824b6a61d089e57691fb10af995c79959c8ec60bf3ffa9
|
File details
Details for the file markdownlint_rs-0.3.16-py3-none-macosx_10_12_x86_64.whl.
File metadata
- Download URL: markdownlint_rs-0.3.16-py3-none-macosx_10_12_x86_64.whl
- Upload date:
- Size: 1.7 MB
- Tags: Python 3, macOS 10.12+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.15 {"installer":{"name":"uv","version":"0.11.15","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5342bea91894c171abd83ad8be303c8aa4908af952ffa94dea13d3fc5b17e106
|
|
| MD5 |
4699bfed4f9a9448a324822b6da69fdf
|
|
| BLAKE2b-256 |
01facbe634a06fbf91ac782f376e3ebbb2323cda2193964e86bf1493bf77cc21
|