Markdown has become the ubiquitous standard for software documentation, Architecture Decision Records (ADRs), and open-source READMEs. However, unlike strongly typed programming languages, Markdown is inherently forgiving. A missing backtick or a misaligned table pipe won’t throw an immediate error in your local text editor, but it can wreak havoc when pushed to production.
When your team relies on automated CI/CD pipelines, unvalidated Markdown can fail MDX builds or silently lose content. This article explores why linting Markdown files is critical, the risks of malformed syntax, and how to validate documentation.
The Risk of Unvalidated Markdown
Markdown parsers attempt to gracefully handle malformed syntax. However, what looks acceptable in a local viewer often breaks when parsed by stricter, component-driven frameworks. For example, Docusaurus MDX is quite strict and produces compilation errors, and Docusaurus onBrokenLinks throws on broken links by default.
Common Markdown defects include:
- Malformed GFM Tables: Malformed GFM tables silently drop extra cells (as per the GFM spec: “the excess is ignored”) (see the GFM spec).
- Unclosed Code Fences: An unclosed code fence swallows the rest of the document.
- Reversed Links: A reversed link
(text)[url]renders as text plus a broken URL (rule MD011). - Skipped Heading Levels (MD001): W3C WAI headings guidance notes that “skipping heading ranks can be confusing and should be avoided where possible”.
Set up markdownlint-cli2
To automatically check your files in your CI pipeline, you can use markdownlint-cli2 based on the markdownlint rules.
Here is a tested .markdownlint-cli2.jsonc config file to commit to your repository:
{
"config": {
"default": true,
"MD004": { "style": "dash" },
"MD013": false,
"MD033": false
},
"globs": ["**/*.md"],
"ignores": ["node_modules/**"]
}
Run locally using npx markdownlint-cli2. Here is a tested output excerpt:
bad.md:3 error MD001/heading-increment Heading levels should only increment by one level at a time [Expected: h2; Actual: h3]
bad.md:5:5 error MD011/no-reversed-links Reversed link syntax [(the docs)[https://example.com/docs]]
bad.md:8:1 error MD004/ul-style Unordered list style [Expected: dash; Actual: asterisk]
bad.md:11 error MD040/fenced-code-language Fenced code blocks should have a language specified [Context: "```"]
bad.md:17:9 error MD056/table-column-count Table column count [Expected: 2; Actual: 3; Too many cells, extra data will be missing]
The command exits with code 1 when it finds errors, and 0 when it is clean (“Summary: 0 issues in 0 files”).
You can use the --fix option locally: npx markdownlint-cli2 --fix. Tested on bad.md, this rewrote (the docs)[https://example.com/docs] to [the docs](https://example.com/docs), wrapped a bare URL in angle brackets, removed trailing spaces and extra blank lines. Rules like MD001, MD040, MD056, MD045 and MD033 are not auto-fixed.
GitHub Actions Integration
You can integrate this directly into your CI pipeline using the markdownlint-cli2-action (Note: This action is not run by us):
- uses: DavidAnson/markdownlint-cli2-action@v24
with:
globs: '**/*.md'
Quick Client-Side Linting
For a quick in-browser check without CI, Utiliome’s Markdown Linter & Quality Checker performs exactly four line-based regex checks: skipped heading levels, empty image alt text, bare URLs, and runs of more than two blank lines. It is not a replacement for full CI linting, but it is useful for quick edits. The tool runs completely in your browser, so the file is not uploaded.
Integrating Linting into Your Workflow: A Pre-Flight Checklist
To maintain pristine documentation, review these common rules:
- Enforce Uniform List Markers (MD004): Ensure all unordered lists use the same character.
- Validate Code Block Languages (MD040): Verify every fenced code block includes a language identifier.
- Eliminate Trailing Whitespace (MD009): Two or more trailing spaces create a hard line break.
- Test Table Structures (MD055/MD056): Ensure table column counts match.
- Verify Heading Spacing (MD022): Confirm that headings are surrounded by blank lines for consistency and readability.
By linting your Markdown files locally and in CI, you streamline peer reviews and keep your documentation pipelines running smoothly.
