DEV Community


Posted on • Updated on

Blog post: Markdownlint

As a follow-up to my post from the 11th. "Giving a talk at CopenhagenJS" I got my presentation accepted. So at the CopenhagenJS February meeting I gave a presentation of Markdownlint. The slides are available at SlideShare, but I thought I would do a small write-up on the topic of the presentation here.

First some basic background history on Markdownlint currently comes in two implementations, the first one in Ruby inspired to the implementation in Node. The Node library in addition has a NPM package with a command line interface (markdownlint), where the Ruby package comes with the command mdl.

The Node based implementation is the one I use on a daily basis so my emphasis will be on this one. I am primarily working in Visual Studio Code now and therefor work with the plugin available there and the command line tool markdownlint both based on the Node implementation. Plugins for both Sublime Text and Atom exist.

Now let's look at some details. The linters both specify a set of rules, when applied can be raised when your Markdown is not adhering to the best-practice suggested. All the rules are identified by a key: MDXXX, where the number identifies a unique part, but instead of your having to work with and remember all these cryptic keys, you can also use human readable aliases.

For my presentation I had prepared a small GitHub repository with all the references used and some examples for pasting and copying, since slides are not always the best reference material for later.

So if we lift the example from the mentioned repository, which is a configuration file for markdownlint (Node).

    "default": true

This is the most basic setup for markdownlint it basically enables all the rules. Now lets get back to the rules.

An example, the rule MD013 also has the alias line-length, which is easier to work with in for example a configuration file. So if we want to disable the line length rule for our linting, we would write as follows:

    "default": true,
    "line-length": false

This is also the configuration used in the example GitHub repository as it looks at the time of writing :-)

The examples given above can be written to a file named: .markdownlint.json, this would mean that the command line tool markdownlint would pick up on this local configuration if available.

So if we enable MD013 (line-length) again, we would get the following output:

$ markdownlint 7: MD013/line-length Line length [Expected: 80; Actual: 129] 13: MD013/line-length Line length [Expected: 80; Actual: 104] 14: MD013/line-length Line length [Expected: 80; Actual: 130] 15: MD013/line-length Line length [Expected: 80; Actual: 167] 17: MD013/line-length Line length [Expected: 80; Actual: 139] 18: MD013/line-length Line length [Expected: 80; Actual: 127]

And just to make a swift comparison, mdl would report the same.

> mdl MD013 Line length MD013 Line length MD013 Line length MD013 Line length MD013 Line length MD013 Line length

A detailed description of the rules is available at

The command line is marvelous, but if you use Visual Studio Code like me you are in for a treat. The plugin for Code is based on the same implementation and hence your projects can use the .markdownlint.json configuration in your editor.

This requires the Markdownlint plugin. The following screenshot demonstrates the report on the same violations of the rule MD013

Visual Studio Code with Markdownlint

And to add even more sugar on top check out how the configuration handling reacts.

Visual Studio Code with Markdownlint JSON configuration file

The plugin comes with builtin handling of the configuration file, so if you hover over a rule, whether it is specified as MDXXX or the related alias, but will be visible. This is quite handy since it lets you write human readable configuration files, but still with the nifty key to the rule listing available for the respective Markdown linters.

But everything is not hunky-dory. The two implementations are close, this mean that you can use the two tools somewhat interchangeably, but they are not fully compliant and compatible.

The Ruby implementation implements 38 rules from MD001 to MD041 and a the rule MD046 Whereas the Node implements 41 rules from MD001 to MD045.

Meaning that the problematic rules are:

  • MD042, only available in the Node implementation
  • MD043, only available in the Node implementation
  • MD044, only available in the Node implementation
  • MD045, only available in the Node implementation
  • MD046, only available in the Ruby implementation

You can see my two gists for Ruby and Node. This might of course change, but that is the state of things at the time of writing, do consult the original listing in the official documentation: Ruby and Node.

If you are aware of the differences and your local Markdownlint configuration does not rely on any of the mentioned holes you can get by with the both tools, but is easier to pick the one of the two which fits your situation the best. Since using both would require to rely on a single configuration so maintaining your rules become a additional work in your project.

Some rules can have additional configuration parameters in addition to enabling and disabling, I have not compared all of these possible configurations between the two implementations for the rule listings for the two implementations, so there might hiding dragons in this area as well.

In addition to the command line and the editor integrations, you can add Markdownlint to your CI/CD setup, you might not be the only contributor to your Markdown products and you cannot assume other are have a similar process or the same tools.

This is a very basic configuration for Travis CI using Markdownlint

language: node_js
- '6'
- npm install -g markdownlint-cli
- markdownlint *.md

This works for Node based projects and you can possibly tweak the example for projects based on other languages.

The following is an example for an XML founded project:

language: node_js
- '6'

  - sudo apt-get install -y libxml2-utils

   - npm install -g markdownlint-cli

    - stage: "Documentation test"
      name: "Markdown test"
      script: markdownlint
    - stage: "Product test"
      name: "XML/XSD test"
      script: xmllint --schema epp.xsd xml/*.xml

Here the product is XML and is therefor linted using another command line tool (xmllint) and the documentation is tested using markdownlint. Both examples have been lifted from my GitHub repository. This renders this beautiful representation.

Travis CI staged builds

Just for the reference here is a untested example for what a Travis configuration for a Markdownlint setup using mdl and in Ruby could look like.

language: ruby
- gem install mdl
- mdl *.md

For the presentation I also talked about what a linter is and the rationale for using linters in general, I do not want to touch on this topic in this blog post, perhaps in a future post. Instead I have dug more into the details around the Markdownlint implementations. In the presentation I also mentioned CommonMark I decided not touch on this topic in this post since it would simply run too long, this might also be a topic for a post in the future.

Preparing the presentation and writing this post has identified several areas in which one could put further effort (projects, contributions, PRs).

  • Implement MD042 in the Ruby implementation
  • Implement MD043 in the Ruby implementation
  • Implement MD044 in the Ruby implementation
  • Implement MD045 in the Ruby implementation
  • Implement MD046 in the Node implementation

  • Experiment with more advanced Travis configurations for more variations of projects and repositories like Rust, Perl whatever might come up

  • Extend the capabilities of the Node and Ruby implementations so configuration files can be shared and then easily maintained

  • Finally the grand finale - contact the authors of the two projects an suggest consolidating a standard for Markdownlint and rules, so the different implementations can be compared based against a baseline outlined as a standard. This is very much inspired by the work made for CommonMark and not completely undoable because of the awesome authors behind the existing implementations and the fact that the Node implementation is inspired and based on the Ruby implementation.

I might propagate a lot of the information from this blog post to the mentioned GitHub repository, do however checkout the repository for a list of references used for this post and the initial presentation and possible future findings, tips and example will go there.

Happy Markdown writing and linting...

Discussion (8)

jonasbn profile image
jonasbn Author

If you run into problems with an error resembling this:

      capturedRules = { ...enabledRules };

I have written up a TIL about the issue and how to fix it.

Shout out to @lauragift21 for her blog post.

lauragift21 profile image
Gift Egwuenu

Oh wow! Thanks for the shout-out! Great post too on Markdownlint haven't used it before now but I'll give it a try :)

burdettelamar profile image
Burdette Lamar

Oh, wow, jonasbn. Great idea.

I'm wondering whether I should consider adding linting to my markdown_helper project and gem.

jonasbn profile image
jonasbn Author


Happy, that you find it great, however unclear on what part. The post ran a bit too long and covered way too much.

The great idea, being adding linting to your tool project?


burdettelamar profile image
Burdette Lamar

Sorry not clear on first try. The great idea is your linter.

Thread Thread
jonasbn profile image
jonasbn Author

Oh, it is not mine, I am just a user - I love linters and I happen to be writing a lot of Markdown, so I currently I am in love with Markdownlint :-)

Using primarily the Node implementation, have played around with the Ruby implementation. Btw. the Ruby implementation should perhaps be what you should look at for possible integration with your project:

bhagerty profile image

Hi Jonas! Fantastic post. You might want to change one typo: "Honky-dory" should be Hunky-dory. Cheers!

jonasbn profile image
jonasbn Author

Corrected, thank you very much