mkdlint

February 17, 2026 · View on GitHub

docs.rs Crates.io Crates.io Total Downloads GitHub License Security Audit GitHub Actions Workflow Status GitHub Issues or Pull Requests GitHub Issues or Pull Requests Codecov

A fast Markdown linter written in Rust, inspired by markdownlint.

Features

  • 64 lint rules (MD001-MD060 + KMD001-KMD011) enforcing Markdown best practices
  • Automatic fixing for 58 rules (90.6% coverage) with --fix flag
  • Helpful suggestions for all rules with actionable guidance
  • VS Code extension with bundled LSP server
  • Language Server Protocol (LSP) for real-time linting in any editor
  • GitHub Action with SARIF Code Scanning, job summaries, and incremental linting
  • Rich error display with source context and colored underlines
  • Multiple output formats -- text (default), JSON, or SARIF
  • Configuration via JSON, YAML, or TOML files with auto-discovery
  • High performance -- zero-copy lines, static strings, conditional parsing
  • Library + CLI -- use as a Rust crate or standalone command-line tool

Installation

VS Code Extension

Install from the VS Code Marketplace or search "mkdlint" in the Extensions panel. The extension bundles the LSP server -- no separate install needed.

CLI Tool

# From crates.io
cargo install mkdlint

# From source
cargo install --path .

# With Homebrew (macOS/Linux)
brew install 192d-Wing/tap/mkdlint

# With Docker
docker run --rm -v $(pwd):/work ghcr.io/192d-wing/mkdlint .

# With pre-commit
# See pre-commit section below

Language Server (LSP)

# Install with LSP feature
cargo install mkdlint --features lsp

# The binary will be available as: mkdlint-lsp

GitHub Action (Quick Start)

Add to your workflow:

- uses: 192d-Wing/mkdlint/.github/actions/mkdlint@main
  with:
    files: '.'

See GitHub Action documentation for full details.

As a Library Dependency

[dependencies]
mkdlint = "0.11"

# With async support
mkdlint = { version = "0.11", features = ["async"] }

# With LSP support
mkdlint = { version = "0.11", features = ["lsp"] }

Auto-Fix Showcase

mkdlint can automatically fix 58 out of 64 rules (90.6%)! Here are some examples:

Before Auto-Fix

#Missing space after hash

![](image.png)

# Title
# Another Title

>Blockquote with  trailing spaces
>
>And blank lines inside

[link](http://example.com)
http://example.com

$ npm install
$ echo "commands with dollar signs"

After mkdlint --fix

# Missing space after hash

![image](image.png)

# Title

## Another Title

> Blockquote with trailing spaces
> And blank lines inside

[link](http://example.com)
<http://example.com>

npm install
echo "commands with dollar signs"

What Gets Fixed Automatically

  • Headings: spacing, levels, ATX style consistency
  • Links & Images: alt text, bare URLs, unused references
  • Lists: indentation, marker consistency, spacing
  • Code: fence styles, dollar sign prefixes, language tags
  • Whitespace: trailing spaces, blank lines, tabs
  • Tables: pipe consistency, surrounding blank lines
  • And much more!

CLI Usage

Basic Commands

# Lint files
mkdlint README.md docs/*.md

# Lint with auto-fix
mkdlint --fix README.md

# Preview what --fix would change (CI-friendly, exits 1 if any fixes exist)
mkdlint --fix-dry-run README.md

# Lint a directory recursively
mkdlint docs/

# Lint from stdin
cat README.md | mkdlint --stdin

# List all available rules with descriptions
mkdlint --list-rules

# Print JSON Schema for config file (useful for editor validation)
mkdlint --generate-schema > schema.json

Configuration Management

# Initialize a new config file with defaults
mkdlint init

# Initialize with custom path and format
mkdlint init --output .mkdlint.yaml --format yaml

# Use a specific config file
mkdlint --config .markdownlint.json README.md

# Enable/disable specific rules on the fly
mkdlint --enable MD001 --disable MD013 README.md

# Combine multiple rule overrides
mkdlint --config base.json --enable MD001 --disable MD033 docs/

Kramdown Preset

For authors writing RFCs and technical documents using Kramdown syntax:

# Enable Kramdown mode via CLI flag
mkdlint --preset kramdown doc.md

# Or set it in your config file
{
  "preset": "kramdown"
}

The kramdown preset:

  • Disables MD033 (inline HTML) — Kramdown IAL syntax {: #id .class key="val"} looks like inline HTML
  • Disables MD041 (first heading required) — RFC preambles often start with metadata, not headings
  • Enables 11 Kramdown-specific rules (off by default):
RuleNameDescription
KMD001definition-list-term-has-definitionDL terms must be followed by : definition
KMD002footnote-refs-defined[^label] refs must have matching [^label]: defs
KMD003footnote-defs-used[^label]: defs must be referenced in the document
KMD004abbreviation-defs-used*[ABBR]: ... defs must appear as text
KMD005no-duplicate-heading-idsHeading IDs (explicit or auto-slug) must be unique
KMD006valid-ial-syntax{: ...} block-level IAL lines must be well-formed
KMD007math-block-delimitersBlock $$ math fences must be matched
KMD008block-extension-syntax{::name}...{:/name} extensions must be opened and closed
KMD009ald-defs-used{:ref-name: attrs} ALDs must be referenced
KMD010inline-ial-syntaxInline *text*{: .class} IAL must be well-formed
KMD011inline-math-balancedInline $...$ math spans must have balanced $ delimiters

You can enable individual KMD rules without the full preset:

{
  "KMD002": true,
  "KMD007": true,
  "KMD010": true
}

GitHub Preset

For documentation hosted on GitHub, using GitHub Flavored Markdown (GFM):

mkdlint --preset github doc.md
{
  "preset": "github"
}

The github preset:

  • Sets MD003 heading style to consistent — GFM renders both ATX and setext, but they should not be mixed
  • Disables MD013 (line length) — long lines are common in GFM tables and URLs
  • Disables MD034 (bare URLs) — GitHub auto-links bare URLs in some contexts

Output Control

# Output in JSON format (machine-readable)
mkdlint --output-format json README.md

# Output in SARIF format (for CI/CD integration)
mkdlint --output-format sarif README.md

# Quiet mode - only show filenames with errors
mkdlint --quiet docs/

# Verbose mode - show detailed error statistics
mkdlint --verbose docs/

# Disable colored output (for CI environments)
mkdlint --no-color README.md

Advanced Usage

# Ignore specific files/patterns
mkdlint --ignore "**/node_modules/**" --ignore "**/.git/**" .

# Combine multiple options
mkdlint --config .mkdlint.json \
       --ignore "build/**" \
       --ignore "dist/**" \
       --fix \
       --verbose \
       .

# Fix with specific rules disabled
mkdlint --fix --disable MD013 --disable MD033 docs/

# Stdin with fix output to stdout
cat README.md | mkdlint --stdin --fix > README_fixed.md

Example Output

mkdlint provides rich error display with source context:

README.md: 42: MD009/no-trailing-spaces Trailing spaces [Expected: 0; Actual: 3]
  42 |
  42 |  This line has trailing spaces
     |                               ^^^

README.md: 58: MD034/no-bare-urls Bare URL used
  58 |
  58 |  Visit https://example.com for more info.
     |        ^^^^^^^^^^^^^^^^^^^

Commands

CommandDescription
mkdlint [FILES...]Lint markdown files (default command)
mkdlint initCreate a new configuration file with defaults

Options

FlagDescription
-f, --fixAutomatically fix violations where possible
--fix-dry-runShow what --fix would change without writing files (exits 1 if changes exist)
-c, --config <PATH>Path to configuration file (.json, .yaml, or .toml)
-o, --output-format <FORMAT>Output format: text (default), json, or sarif
--ignore <PATTERN>Glob pattern to ignore (can be repeated)
--stdinRead input from stdin instead of files
--list-rulesList all available linting rules with descriptions
--enable <RULE>Enable specific rule (can be repeated)
--disable <RULE>Disable specific rule (can be repeated)
--generate-schemaPrint a JSON Schema for the config file and exit
-v, --verboseShow detailed output with error statistics
-q, --quietQuiet mode - only show filenames with errors
--no-colorDisable colored output
--no-inline-configDisable inline configuration comments

VS Code Extension

Install from the Marketplace or use the bundled extension in editors/vscode/.

Features:

  • Real-time diagnostics as you type
  • Quick-fix code actions (Ctrl+.)
  • "Fix All Issues" command
  • Status bar with error/warning counts
  • Respects .markdownlint.json config

Settings:

SettingDescriptionDefault
mkdlint.enableEnable/disable lintingtrue
mkdlint.pathOverride mkdlint-lsp binary pathnull
mkdlint.trace.serverLSP trace level for debuggingoff

Language Server Protocol (LSP)

mkdlint includes a full-featured Language Server for real-time linting in your editor.

Neovim Setup

require('lspconfig').mkdlint.setup{
  cmd = { '/path/to/mkdlint-lsp' },
  filetypes = { 'markdown' },
  root_dir = require('lspconfig.util').root_pattern('.markdownlint.json', '.git'),
}

Other Editors

Any editor with LSP support can use mkdlint-lsp. The server uses stdio for communication and supports:

  • textDocument/didOpen, didChange, didSave, didClose
  • textDocument/codeAction (for individual auto-fixes)
  • workspace/executeCommand (for "Fix All" command)
  • Full document synchronization

CI/CD Integration

GitHub Action

name: Lint Markdown
on: [push, pull_request]

jobs:
  lint:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      security-events: write

    steps:
      - uses: actions/checkout@v4

      - uses: 192d-Wing/mkdlint/.github/actions/mkdlint@main
        with:
          files: '.'

Features:

  • Pre-built binaries for Linux, macOS, Windows (x86_64, aarch64)
  • Automatic binary caching (10-100x faster subsequent runs)
  • SARIF output with automatic Code Scanning upload
  • Rich job summary with error counts and top violated rules
  • Incremental linting -- only lint changed files in PRs (changed-only: true)
  • Performance timing in outputs (duration-ms)
  • Auto-fix support with commit integration

See full documentation for all options.

pre-commit

Add to your .pre-commit-config.yaml:

repos:
  - repo: https://github.com/192d-Wing/mkdlint
    rev: main
    hooks:
      - id: mkdlint

Docker

# Lint current directory
docker run --rm -v $(pwd):/work ghcr.io/192d-wing/mkdlint .

# Lint with auto-fix
docker run --rm -v $(pwd):/work ghcr.io/192d-wing/mkdlint --fix .

# Lint specific files
docker run --rm -v $(pwd):/work ghcr.io/192d-wing/mkdlint README.md docs/

Library Usage

use mkdlint::{lint_sync, apply_fixes, LintOptions};

let options = LintOptions {
    files: vec!["README.md".to_string()],
    ..Default::default()
};

let results = lint_sync(&options).unwrap();
for (file, errors) in results.iter() {
    for error in errors {
        println!("{}: {}", file, error);
    }
}

Auto-fixing

use mkdlint::{lint_sync, apply_fixes, LintOptions};
use std::collections::HashMap;

let content = "# Title\n\nSome text   \n";
let mut strings = HashMap::new();
strings.insert("test.md".to_string(), content.to_string());

let options = LintOptions { strings, ..Default::default() };
let results = lint_sync(&options).unwrap();

if let Some(errors) = results.get("test.md") {
    let fixed = apply_fixes(content, errors);
    println!("{}", fixed); // trailing whitespace removed
}

Configuration

Create a .markdownlint.json (or .yaml / .toml) file:

{
  "default": true,
  "MD013": { "line_length": 120 },
  "MD033": false
}

Rules can be enabled/disabled by name ("MD013") or alias ("line-length"). Pass a boolean to enable/disable, or an object to configure options.

Rules

RuleAliasDescriptionFixable
MD001heading-incrementHeading levels should increment by oneYes
MD003heading-styleHeading styleYes
MD004ul-styleUnordered list styleYes
MD005list-indentInconsistent indentation for list itemsYes
MD007ul-indentUnordered list indentationYes
MD009no-trailing-spacesTrailing spacesYes
MD010no-hard-tabsHard tabsYes
MD011no-reversed-linksReversed link syntaxYes
MD012no-multiple-blanksMultiple consecutive blank linesYes
MD013line-lengthLine length
MD014commands-show-outputDollar signs used before commandsYes
MD018no-missing-space-atxNo space after hash on atx headingYes
MD019no-multiple-space-atxMultiple spaces after hash on atx headingYes
MD020no-missing-space-closed-atxNo space inside hashes on closed atx headingYes
MD021no-multiple-space-closed-atxMultiple spaces inside hashes on closed atx headingYes
MD022blanks-around-headingsHeadings should be surrounded by blank linesYes
MD023heading-start-leftHeadings must start at the beginning of the lineYes
MD024no-duplicate-headingNo duplicate heading contentYes
MD025single-titleSingle title / single h1Yes
MD026no-trailing-punctuationTrailing punctuation in headingYes
MD027no-multiple-space-blockquoteMultiple spaces after blockquote symbolYes
MD028no-blanks-blockquoteBlank line inside blockquoteYes
MD029ol-prefixOrdered list item prefixYes
MD030list-marker-spaceSpaces after list markersYes
MD031blanks-around-fencesFenced code blocks should be surrounded by blank linesYes
MD032blanks-around-listsLists should be surrounded by blank linesYes
MD033no-inline-htmlInline HTML
MD034no-bare-urlsBare URL usedYes
MD035hr-styleHorizontal rule styleYes
MD036no-emphasis-as-headingEmphasis used instead of a headingYes
MD037no-space-in-emphasisSpaces inside emphasis markersYes
MD038no-space-in-codeSpaces inside code span elementsYes
MD039no-space-in-linksSpaces inside link textYes
MD040fenced-code-languageFenced code blocks should have a language specifiedYes
MD041first-line-headingFirst line in a file should be a top-level headingYes
MD042no-empty-linksNo empty linksYes
MD043required-headingsRequired heading structure
MD044proper-namesProper names should have correct capitalizationYes
MD045no-alt-textImages should have alternate textYes
MD046code-block-styleCode block styleYes
MD047single-trailing-newlineFiles should end with a single trailing newlineYes
MD048code-fence-styleCode fence styleYes
MD049emphasis-styleEmphasis styleYes
MD050strong-styleStrong styleYes
MD051link-fragmentsLink fragments should be valid
MD052reference-links-imagesReference links and images should use a defined labelYes
MD053link-image-reference-definitionsLink and image reference definitions should be neededYes
MD054link-image-styleLink and image styleYes
MD055table-pipe-styleTable pipe styleYes
MD056table-column-countTable column count
MD058blanks-around-tablesTables should be surrounded by blank linesYes
MD059emphasis-marker-style-mathEmphasis marker style in mathYes
MD060dollar-in-code-fenceDollar signs in fenced code blocksYes

Kramdown Extension Rules (off by default)

RuleAliasDescriptionFixable
KMD001definition-list-term-has-definitionDefinition list terms must be followed by a definition
KMD002footnote-refs-definedFootnote references must have matching definitions
KMD003footnote-defs-usedFootnote definitions must be referenced in the document
KMD004abbreviation-defs-usedAbbreviation definitions should be used in document text
KMD005no-duplicate-heading-idsHeading IDs must be unique within the documentYes
KMD006valid-ial-syntaxIAL (Inline Attribute List) syntax must be well-formedYes
KMD007math-block-delimitersMath block $$ delimiters must be matchedYes
KMD008block-extension-syntaxBlock extensions must be properly opened and closedYes
KMD009ald-defs-usedAttribute List Definitions must be referenced in the documentYes
KMD010inline-ial-syntaxInline IAL syntax must be well-formedYes
KMD011inline-math-balancedInline math spans must have balanced '$' delimiters

58 of 64 rules have auto-fix support (90.6% coverage).

License

Apache-2.0