CLI tool
DavidAnson/markdownlint avatar
DavidAnson/markdownlint

markdownlint: a CommonMark style checker for Node.js projects

A Node.js style checker and lint tool for Markdown/CommonMark files.

6,364 stars946 forksJavaScriptMIT

At a glance

What is it?
markdownlint is the library behind most Markdown linting in the Node.js ecosystem, and it deliberately ships no command-line interface. Here is what it does, how to install and configure it, and where it stops being the right tool.
Who is it for?
Adopt markdownlint when you are writing JavaScript or TypeScript and want the rules to run inside your own build or test step, or when you want the same rule engine that the VS Code extension and the CLI wrappers use. Do not adopt it if you expected a binary: the package has no command-line interface, and the README points to markdownlint-cli and markdownlint-cli2 for that.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 1 day ago.
What is it written in?
Mainly JavaScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 30, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The problem markdownlint solves, and who it is built for

Markdown's flexibility is the source of the problem. The README puts it plainly: many styles are possible, so formatting becomes inconsistent, and some constructs do not work well in every parser. A heading that jumps from level one to level three renders differently depending on the renderer. A bare URL, a hard tab, or a list marker with the wrong number of trailing spaces may be valid CommonMark and still produce output nobody intended.

markdownlint is a static analysis tool for Node.js with a library of rules that enforce standards and consistency across Markdown files. The rules are named, numbered and individually documented: MD009 no-trailing-spaces, MD010 no-hard-tabs, MD013 line-length, MD034 no-bare-urls, MD033 no-inline-html. That naming matters in practice. When a check fails you get an identifier you can look up, disable, or configure, rather than an anonymous complaint.

The intended audience is a project that already has Node.js in its toolchain. Because the package is a library, the natural home for it is a test script, a build step, or an editor extension. Teams that write documentation in a repository that has no JavaScript at all are not the target, and the README's Related section is honest about that: it lists Ruby's mdl gem for that world.

How the micromark parser and the rule set fit together

The mechanism is a parse followed by a rule pass. markdownlint uses the micromark parser and honors the CommonMark specification, so the syntax tree it reasons about is the one the spec defines rather than one the tool invented. On top of that it supports GitHub Flavored Markdown syntax such as autolinks and tables, plus directives, footnotes and math syntax, all implemented through micromark extensions.

That extension model is where the interesting constraint lives. The README states that inline directives are not supported, and gives the reason: over-matching. A directive syntax that can appear inside a paragraph is hard to distinguish from ordinary text, so the project chose not to accept it. If your documentation relies on inline directive syntax, markdownlint will not see it as a directive.

The rules themselves are the second half. They cover heading structure (MD001 heading-increment, MD025 single-title, MD024 no-duplicate-heading), list formatting (MD004 ul-style, MD007 ul-indent, MD029 ol-prefix), whitespace (MD012 no-multiple-blanks, MD031 blanks-around-fences, MD032 blanks-around-lists) and link syntax (MD011 no-reversed-links, MD034 no-bare-urls). Each rule has a documentation page under doc/ and an alias, which is why you can write a config key as either MD013 or line-length.

Ambiguity has a defined resolution. The README names CommonMark and the GitHub Flavored Markdown Spec as the authoritative references when the rules and the specs disagree. That is a design decision worth noting: the tool is not the source of truth, the specifications are.

Installing markdownlint and running the library for the first time

The README gives one installation command. It installs the library as a development dependency, which is the right scope for a linter that runs in a build or test step.

bash
npm install markdownlint --save-dev

After that, the package exposes several entry points rather than a binary. The package.json exports map shows "./sync", "./promise", "./async" and the default ".", plus "./helpers" and four style presets: "./style/all", "./style/cirosantilli", "./style/prettier" and "./style/relaxed". Choosing between the synchronous, promise and async entry points is the first real decision, because it determines the shape of the call you write.

The repository also ships an example directory and a demo directory with a browser build (demo/browser-exports.mjs, demo/default.js, demo/webpack.config.mjs). The README links an interactive in-browser playground at dlaa.me/markdownlint for learning and exploring the rules without installing anything. If you want to see what MD013 line-length does to a paragraph before you add it to a config file, that is the fastest path.

For command-line use, the README does not point at this package. It points at markdownlint-cli and markdownlint-cli2, two separate Node.js command-line interfaces maintained outside this repository. The VS Code extension, the Sublime Text and Vim integrations, the GitHub Action and the Super-Linter action are likewise listed as related projects, not as part of this package.

Configuring rules: .markdownlint.json, presets and style files

Configuration is data, not code. The repository root contains a .markdownlint.json file, which is the conventional location and name for a project's rule settings. Because the package exports style presets as JSON, a config can extend or approximate one of them instead of listing every rule by hand. The four presets are all, cirosantilli, prettier and relaxed; the names tell you what they are for, and the README does not document what each one contains, so you have to read the JSON in style/ to know.

Rule keys accept either the number or the alias, which is why search queries about MD013 and about line-length are both common. MD013 is the rule most teams end up tuning, because a line-length limit interacts with prose, tables and code blocks differently, and the rule has parameters for that.

The disabling mechanism is a comment inside the Markdown itself. The README's own rule list uses it: the line-length rule is turned off around a block of long link definitions with a markdownlint-disable comment. That is a per-file, per-region escape hatch, and it is the reason the tool can be adopted incrementally on a repository that does not pass yet.

The schema directory and the build-config script in package.json are how the config format is generated and validated. If you want to know exactly which keys a rule accepts, the schema is the machine-readable answer rather than the prose documentation.

Where markdownlint is the wrong tool

The clearest limitation is the missing command-line interface. There is no bin entry in the package.json, and the README's Install section stops at npm install. Anyone who runs npx markdownlint expecting this package to lint a directory has picked the wrong package; the CLI wrappers are separate projects with their own names and their own configuration conventions. That split is deliberate, but it means a bug report about flag parsing belongs somewhere else.

The second limitation is inline directives. The README states they are not supported, to avoid confusion caused by over-matching. Documentation systems that embed inline directives inside sentences will get no directive awareness from this parser.

The third is scope. This is a style and consistency checker, not a prose linter and not a link checker that fetches URLs. MD034 flags a bare URL because of how it renders, not because the URL is dead. If your actual problem is that links rot, or that the writing is unclear, markdownlint will report a clean file and you will still have the problem.

Finally, the rule set is opinionated by construction. The initial rules, rule documentation and test cases came from Mark Harrison's Ruby markdownlint project, so the defaults encode someone else's formatting preferences. Adopting the defaults on an existing repository produces a large first diff, and the only way through is the disable comment or a config that turns rules off one at a time.

markdownlint compared with Prettier and with Vale

The comparison people search for most is markdownlint versus Prettier, and the difference is in what each one does with a file. Prettier rewrites. It parses your Markdown and prints it back in a single canonical style, so the output is a new file. markdownlint reports. It parses your Markdown, runs rules over the tree, and returns a list of rule violations with positions; the file on disk is unchanged unless you act on the report. The package.json here exports a style/prettier preset, which is the reconciliation path: you can configure markdownlint to accept Prettier's formatting and use it for the rules Prettier does not cover.

The other comparison is markdownlint versus Vale. Vale is a prose linter: it checks wording against style guides and vocabulary rules. markdownlint checks the markup, not the sentences. A document can pass every MD rule and still be unreadable, and a document can be beautifully written and fail MD013 on every paragraph. They are not substitutes, and the README does not present markdownlint as a prose tool.

Within the Markdown linting space itself, the real alternative is the Ruby mdl gem, which the README lists under Related. The shared lineage is documented: markdownlint was inspired by and heavily influenced by Mark Harrison's project, and the initial rules and test cases came from it. The difference in approach is the parser and the ecosystem. mdl is Ruby and predates CommonMark's current tooling; markdownlint is Node.js, uses micromark, and honors CommonMark and GFM as authoritative references.

Maintenance, licence and what upgrading costs

The package is MIT licensed, which is stated in both the README badge and the package.json license field. For a linter that runs in a build step, that is about as permissive as it gets: you can vendor it, modify it, or ship it inside a larger product. This is a description of the licence text, not legal advice, and the LICENSE file in the repository is the version that governs.

The repository is not archived and the last push was on 2026-09-22. The package.json reports version 0.41.1, and the zero-major version number is the thing to weigh. Under semantic versioning a 0.x project is allowed to break compatibility in a minor release, so an upgrade from 0.41 to 0.42 is not guaranteed to be a no-op for your config or your API calls. The CHANGELOG.md at the repository root is where those changes are recorded, and it is the file to read before bumping the dependency.

The upgrade cost is mostly config drift rather than code. Rules get added, defaults change, and a repository that passed on the old version can fail on the new one. Because the entry points are split across ./sync, ./promise and ./async, an API-level change can also mean editing the import path rather than the call. The style presets are exported as JSON files, so a preset change is visible as a diff in style/ rather than as a silent behaviour shift.

Editorial conclusion

Adopt markdownlint when you are writing JavaScript or TypeScript and want the rules to run inside your own build or test step, or when you want the same rule engine that the VS Code extension and the CLI wrappers use. Do not adopt it if you expected a binary: the package has no command-line interface, and the README points to markdownlint-cli and markdownlint-cli2 for that. Before committing, verify what your parser actually supports, because the README states that inline directives are not supported, and check the rule list against your existing files, since the default set will flag formatting that is already in your repository.

Frequently asked questions

How do I install markdownlint?

The README gives a single command: npm install markdownlint --save-dev. That installs the library as a development dependency; there is no separate installer and no global binary. For a command-line interface you need markdownlint-cli or markdownlint-cli2, which the README lists as related projects.

What is markdownlint and what does it do?

It is a Node.js static analysis tool for Markdown and CommonMark files, with a library of rules that enforce standards and consistency. It parses with micromark, supports GitHub Flavored Markdown syntax, and reports rule violations such as trailing spaces, hard tabs, inconsistent list indentation and bare URLs.

How can I disable a markdownlint rule?

The README's own rule list turns off the line-length rule around a block of long lines using a markdownlint-disable comment inside the Markdown. That comment is the per-file, per-region escape hatch. Rules can also be turned off in the configuration file, where each key accepts either the rule number or its alias.

How does markdownlint compare with Prettier?

Prettier rewrites a Markdown file into one canonical style, while markdownlint reports rule violations and leaves the file unchanged. The package ships a style/prettier preset, which lets markdownlint accept Prettier's formatting so the two can be used together.

Is markdownlint safe to add to a project?

It is an MIT-licensed static analysis library that reads Markdown files and returns rule violations; the README describes no network access or file rewriting. The package is not archived and the last push was on 2026-09-22, with package.json reporting version 0.41.1.

Official sources

  1. DavidAnson/markdownlint on GitHub
  2. Issues
  3. License: MIT
  4. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/davidanson-markdownlint.svg)](https://hysenlabs.com/projects/davidanson-markdownlint)