Markdown Formatter: Auto-Format Markdown Files

September 11, 2026 · 10 min read

Markdown Formatter: Auto-Format Markdown Files Consistently

A markdown formatter rewrites the source of a .md file so every heading, list, table, and emphasis mark follows one style, without changing what the document says. This guide shows exactly what a formatter changes and compares Prettier, mdformat, markdownlint, and dprint on the same messy file. It then sets up format-on-save in VS Code and Neovim. If you're looking for the syntax itself, start with the Markdown cheat sheet.

What Does a Markdown Formatter Actually Change?

A formatter is to Markdown what Prettier is to JavaScript or Black is to Python: an opinionated tool that takes any valid input and emits one canonical layout. It doesn't check your prose or your links. It normalises the markup so diffs stay small and nobody argues about asterisks versus dashes in code review.

Two phrases get mixed up here. Markdown text formatting (or markdown text format) means the syntax you type to make text bold, italic, or a heading. Markdown file formatting, or md formatting, means the layout of the source file: which marker, how many blank lines, how wide the table columns are. A formatter only touches the second kind. Your **bold** stays bold; the tool decides whether it's written with asterisks or underscores.

We ran this deliberately messy file through four tools. It has a setext heading, mixed list markers, an ordered list that starts at 3, and trailing spaces. It also has an unpadded table, an asterisk rule, an indented code block, and a heading with no blank line above it.

Release Notes
=============

Version 2.1 ships today.   
* Faster __export__
* Dark _mode_
  * nested item
3. third
4. fourth

|Region|Deals|
|--|--|
|EMEA|14|
|Asia Pacific|9|
***
    indented code
## No blank line above

Here's what mdformat 1.0.0 with the tables plugin produced:

# Release Notes

Version 2.1 ships today.

- Faster __export__
- Dark _mode_
  - nested item

3. third
1. fourth

| Region       | Deals |
| ------------ | ----- |
| EMEA         | 14    |
| Asia Pacific | 9     |

______________________________________________________________________

```
indented code
```

## No blank line above

Every change in that diff is a category a formatter can own. The list: heading style (setext to ATX), list marker (* to -), ordered-list numbering, blank lines around blocks, trailing whitespace, table column padding, thematic break style, and code block style. The rendered HTML is identical before and after. The __export__ emphasis was left alone because mdformat doesn't normalise emphasis markers; Prettier does, as the next section shows.

How Do Prettier, mdformat, markdownlint, and dprint Compare?

The same input, four tools, and four different opinions. All numbers are from our run on 11 September 2026.

ToolLanguageSetext to ATX* to -Table padding__ to **Rule styleWraps prose
Prettier 3.9.6NodeNoYesYesYes---proseWrap, default preserve
mdformat 1.0.0PythonYesYesWith mdformat-tablesNo70 underscores--wrap, default keep
markdownlint-cli2 0.23.2 --fixNodeNo (reports it)Only with MD004 setNoNoNoNo
dprint markdown pluginRustYesConfigurableYesConfigurable---textWrap, default maintain

Prettier formats Markdown out of the box, which is why it's the default in most JavaScript repos. It changed __export__ to **export**, aligned the table, turned *** into ---, and kept the setext heading. Its proseWrap option defaults to preserve because some renderers, GitHub comments among them, treat line breaks as significant.

mdformat is the strictest of the four. It's CommonMark only by design, so tables, task lists, and footnotes need plugins: pipx install mdformat then pipx inject mdformat mdformat-gfm. Its style rules are documented in full, including the one people trip over: hard line breaks become a trailing backslash, never two spaces, because two spaces are invisible.

markdownlint is a linter first. markdownlint-cli2 --fix repaired the blank lines, trailing whitespace, and ordered-list numbering in our sample, left the table and emphasis untouched, and reported the mixed heading style as an error it can't fix. Our Markdown lint guide covers its rule set. Pair it with a real formatter rather than expecting it to be one.

dprint is the answer to "markdown formatter rust". dprint add markdown pulls the WebAssembly plugin, and the config exposes emphasisKind, strongKind, list.unorderedMarker, and textWrap. It also formats fenced code blocks with whatever other dprint plugins you've installed.

Our preference is Prettier where Node already exists and mdformat everywhere else. The one caveat with mdformat: run it on an Obsidian or Hugo vault without the matching plugins and it will backslash-escape wikilinks and shortcodes it doesn't recognise. Add mdformat-wikilink or mdformat-frontmatter, or exclude those folders.

Try the Markdown Formatter Online

The editor below is the site's own formatter, pre-filled with the messy sample. Click the Format button to see the result. In our test it strips trailing whitespace, converts * list markers to -, renumbers ordered lists to 1., collapses runs of blank lines, and inserts a blank line before headings. It leaves setext headings, emphasis markers, and table padding as they are. It also removes indentation from nested list items, so check nested lists afterwards.

Release Notes

Version 2.1 ships today.

  • Faster export
  • Dark mode
  1. third
  2. fourth
Region Deals
EMEA 14
Asia Pacific 9

No blank line above

28 words187 characters15 lines
Markdown

The full-page Markdown formatter tool adds copy and download buttons. For table-only cleanup, the Markdown table generator pads columns without touching the rest of the file.

Configuring Each Markdown Formatter

One config snippet per tool, all doing the same thing: dash bullets, asterisk emphasis, and no prose wrapping.

Prettier, in .prettierrc:

{
  "proseWrap": "preserve",
  "printWidth": 80
}

Prettier doesn't expose marker options; dashes and asterisks are its fixed choices. mdformat takes its options on the command line or in .mdformat.toml:

wrap = "keep"
number = true
end_of_line = "lf"

number = true renumbers ordered lists consecutively. Without it, mdformat keeps the first item's number and writes 1. for the rest, which is what you saw in the sample above. That "non-numbering" style is deliberate: inserting an item in the middle changes one line instead of every line below it. Our Markdown lists guide explains why both styles render the same.

dprint, in dprint.json:

{
  "markdown": {
    "lineWidth": 80,
    "textWrap": "maintain",
    "emphasisKind": "asterisks",
    "strongKind": "asterisks",
    "list.unorderedMarker": "dashes"
  },
  "plugins": ["https://plugins.dprint.dev/markdown-0.x.x.wasm"]
}

markdownlint, in .markdownlint.json, if you want its --fix to align with the formatter's choices:

{
  "MD004": { "style": "dash" },
  "MD029": { "style": "one" },
  "MD049": { "style": "asterisk" }
}

Format Markdown on Save in VS Code and Neovim

In VS Code, install the Prettier extension, then set it as the Markdown formatter and turn on format-on-save in settings.json. The keys come from the VS Code formatting docs:

{
  "[markdown]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode",
    "editor.formatOnSave": true
  }
}

Shift+Alt+F (Shift+Option+F on macOS) formats on demand. The rest of the Markdown-specific VS Code setup is in our VS Code Markdown guide.

In Neovim, conform.nvim runs any of the four tools as a formatter. A minimal Lua config that formats on save with Prettier, falling back to mdformat:

require("conform").setup({
  formatters_by_ft = { markdown = { "prettier", "mdformat", stop_after_first = true } },
  format_on_save = { timeout_ms = 1000, lsp_format = "fallback" },
})

For CI and teams, a pre-commit hook guarantees nobody commits unformatted files. mdformat ships one:

repos:
  - repo: https://github.com/executablebooks/mdformat
    rev: 1.0.0
    hooks:
      - id: mdformat
        additional_dependencies: [mdformat-gfm, mdformat-tables]

Run mdformat --check . or prettier --check "**/*.md" in the pipeline to fail the build instead of rewriting files.

Formatter vs Linter: Which Do You Need?

A formatter rewrites style and never complains. A linter complains and, at best, fixes a subset. They overlap on whitespace and list markers, and diverge everywhere else. markdownlint will tell you a heading skipped a level, a link is bare, or a line is over 80 characters. No formatter cares about any of that. Prettier will realign a table; markdownlint can't.

The combination that works in practice is one formatter running on save and one linter running in CI. Set the linter's style rules to match the formatter's output, as in the .markdownlint.json above, or the two will fight over every ordered list.

Common Markdown Formatting Mistakes

Running a formatter on a vault or site source without plugins. mdformat escapes [[wikilinks]], Hugo shortcodes, and YAML front matter it doesn't understand. Install the plugin for your platform first, or scope the formatter to plain docs folders.

Expecting markdownlint --fix to format tables. It won't. In our test it fixed blank lines, trailing spaces, and list numbers, and reported the heading style as something it can't fix. Use it after a formatter, not instead of one.

Turning on proseWrap: always for content that goes to GitHub comments or Slack. Those renderers keep your line breaks, so hard-wrapped paragraphs show up with ragged breaks. Keep preserve for chat-bound text.

Markdown Formatter FAQ

Pick one markdown formatter, wire it to save and to CI, and Markdown formatting stops being a review topic. Prettier is the low-friction default, mdformat has the clearest rules, and dprint is the fastest. markdownlint belongs alongside whichever you choose rather than in its place. Paste any file into the editor and run the formatter to see the changes before committing to a tool.