What is Markdownlint?
markdownlint is a linter for Markdown files that enforces specific formatting and syntax rules. It checks your Markdown content against a set of predefined rules, each identified by a unique code like MD001 or MD041. The tool is available as a Node.js library (markdownlint), a command-line interface (markdownlint-cli), and a faster alternative CLI (markdownlint-cli2). Editor integrations such as the VS Code extension make it possible to see violations directly in your workspace. The goal is not to impose a single “correct” style, but to help teams maintain consistency, avoid broken syntax, and keep documentation clean and readable.
Why Markdown Linting Matters
🚀 Deploy your AI agent in 10 minutes
Managed Hermes hosting. Zero DevOps. 100M tokens/mo included.
Try it free →Markdown is often used for technical documentation, README files, wikis, and blog posts. Without a linter, small inconsistencies can creep in — double‑spacing after headings, mixed bullet styles, broken links, or raw HTML that could break rendering in some contexts. A linter brings several benefits:
- Consistency across contributors – everyone follows the same conventions.
- Readability – clean structure makes content easier to scan and maintain.
- Preventing rendering issues – some Markdown parsers are strict about blank lines around headings or code blocks.
- Automated quality gates – linting can run in CI/CD to block poorly formatted documentation from being merged.
- Reduced review overhead – reviewers don’t need to point out style nits manually.
Getting Started with Markdownlint
The quickest way to get started is with markdownlint-cli. You can install it globally or as a dev dependency in a Node.js project.
# Install globally
npm install -g markdownlint-cli
# Or add to a project
npm install --save-dev markdownlint-cli
Using markdownlint-cli
Lint all Markdown files in the current directory recursively:
markdownlint '**/*.md'
Lint a single file and see the output:
markdownlint README.md
If there are violations, the CLI prints file, line, rule code, and a description. For example:
README.md:3:1 MD002 First header should be a h1 header
README.md:10 MD009 Trailing spaces
README.md:15:1 MD032 Lists should be surrounded by blank lines
You can also use the --config flag to point to a configuration file:
markdownlint -c .markdownlint.json 'docs/**/*.md'
Using markdownlint as a Library
For custom tooling, you can use the markdownlint package directly. It provides synchronous and asynchronous APIs.
const markdownlint = require('markdownlint');
const options = {
files: ['README.md'],
config: {
default: true,
MD013: false, // disable line length rule
},
};
markdownlint(options, (err, result) => {
if (err) {
console.error(err);
return;
}
console.log(JSON.stringify(result, null, 2));
});
The result object contains arrays of violations per file, making it easy to integrate with build scripts or reporting tools.
Configuration Files
Configuration tells markdownlint which rules to enable/disable and how to tune them. The configuration file can be in JSON, YAML, or even placed inside package.json under a markdownlint key. By default the CLI looks for .markdownlint.json, .markdownlint.yaml, .markdownlint.yml, or .markdownlintrc (in that order). You can also specify a custom path with -c.
Example .markdownlint.json
{
"default": true,
"MD001": false,
"MD013": {
"line_length": 120,
"heading_line_length": 80,
"code_block_line_length": 120
},
"MD024": {
"siblings_only": true
},
"MD033": false,
"MD041": false
}
Explanation:
"default": true enables all rules except those explicitly disabled. MD001 (heading‑increment) is turned off entirely. MD013 (line‑length) is customized to allow up to 120 characters, with separate limits for headings and code blocks. MD024 (duplicate headings) is set to only flag duplicates at the same level as siblings. MD033 (inline HTML) and MD041 (first line heading) are disabled.
Using YAML Configuration
# .markdownlint.yaml
default: true
MD001: false
MD013:
line_length: 120
heading_line_length: 80
code_block_line_length: 120
MD024:
siblings_only: true
MD033: false
MD041: false
YAML is often preferred for its readability and ability to include comments.
Disabling Rules Inline
Sometimes you need to suppress a rule for a specific section. Use HTML comments with markdownlint-disable:
Text that triggers a rule...
This paragraph has trailing spaces and raw html without complaint.
Normal linting resumes.
You can also disable all rules temporarily with <!-- markdownlint-disable --> and re-enable with <!-- markdownlint-enable -->. This is useful for auto‑generated content or edge cases.
Common Rules and Customization
Here are some of the most frequently encountered rules and how to configure them.
- MD001 – heading-increment: Headings must start at h1 and increase by one level at a time. Disable if you intentionally skip levels for layout.
- MD007 – ul-indent: Unordered list indentation should be 2 spaces (default). Customize with
{"indent": 4}. - MD010 – no-hard-tabs: Hard tabs are forbidden. Use spaces instead.
- MD013 – line-length: Line length limit. Set
"line_length"or disable withfalse. - MD022 – blanks-around-headings: Requires blank lines before and after headings. Customize with
{"lines_above": 1, "lines_below": 1}. - MD024 – multiple-headings-with-same-content: Duplicate heading names are flagged. Use
"siblings_only": trueto allow duplicates in different sections. - MD026 – trailing-punctuation: No punctuation at the end of headings. Exceptions can be set:
{"punctuation": "!;:."}. - MD033 – no-inline-html: Disallow raw HTML. Enable if you want pure Markdown portability.
- MD041 – first-line-heading: First line should be a top‑level heading (h1). Disable for files that start with front‑matter.
Configuring Rule Options
Many rules accept an object with parameters. Here is an example that tunes several rules:
{
"MD007": { "indent": 4 },
"MD013": {
"line_length": 100,
"code_block_line_length": 100,
"tables": false
},
"MD022": { "lines_above": 2, "lines_below": 1 },
"MD026": { "punctuation": ".,;:!?" },
"MD030": { "ol_single": 2, "ol_multi": 1, "ul_single": 2, "ul_multi": 1 },
"MD035": { "style": "---" }
}
Always consult the official rule documentation for the exact parameter names and defaults.
Integrating with Editors and CI/CD
Real‑time feedback while writing is invaluable. The VS Code extension (davidanson.vscode-markdownlint) uses the same engine and configuration. After installing the extension, create a .markdownlint.json in your workspace root. The extension will pick it up automatically and underline violations in the editor.
For CI/CD pipelines, add a lint step to your build script:
# In package.json scripts
"scripts": {
"lint:markdown": "markdownlint '**/*.md' -c .markdownlint.json"
}
Then run it as part of your pipeline:
npm run lint:markdown
Most CI services (GitHub Actions, GitLab CI, Jenkins) will treat a non‑zero exit code as a failure, blocking the merge.
Setting Up a Pre-commit Hook
To prevent un‑linted files from being committed, use lint-staged together with husky:
// package.json (or .lintstagedrc.json)
{
"lint-staged": {
"*.md": [
"markdownlint -c .markdownlint.json"
]
}
}
Then register a pre‑commit hook with husky:
npx husky add .husky/pre-commit "npx lint-staged"
Now every commit will automatically lint staged Markdown files and abort if violations are found.
Best Practices
- Start with the defaults – enable
"default": trueand only disable rules you have a clear reason to ignore. - Document why a rule is disabled – use comments in the config file (YAML) or a separate
MARKDOWNLINT_REASON.mdso future maintainers understand the decision. - Share a canonical config – commit the configuration file to the repository so every contributor and CI uses the same set of rules.
- Use a shared config package – organizations like Microsoft publish opinionated configs (
@microsoft/markdownlint-config) that you can extend. - Avoid inline disable comments for style preferences – if a rule is consistently triggering false positives, adjust the rule options or disable it globally rather than sprinkling comments everywhere.
- Customize line length realistically – set
MD013to a value that fits your documentation workflow (often 80–120 characters). Disabling it entirely can lead to messy diffs. - Lint early and often – integrate linting into your editor and pre‑commit hooks so formatting stays clean without slowing down reviews.
- Version‑lock your linter – pin the version of
markdownlint-cliin yourpackage.jsonto avoid unexpected rule changes breaking your pipeline.
Conclusion
markdownlint turns Markdown style guidelines into an automated, enforceable system. With a single configuration file you can define exactly how headings, lists, whitespace, and other elements should behave. The tool fits naturally into local editors, pre‑commit hooks, and CI/CD pipelines, giving you a consistent documentation experience across your entire team. Start with the default rules, tune the ones that conflict with your existing style, and commit the configuration file – you’ll immediately see cleaner, more maintainable Markdown across your project.