OpenKnowledge
Content rules

markdownlint

The markdownlint linter for content rules — VS Code-parity defaults, your project's native `.markdownlint.*` config, and a no-code browser for all 53 rules.

markdownlint is the content-rules linter for markdown style — the standard markdownlint engine, the same one behind markdownlint-cli2 and the popular editor extensions.

This page covers its defaults, how it reads your project's config, and the built-in rule browser. For the linter-agnostic parts — where problems appear, the Problems panel, ok lint, and agent advisories — see the content rules overview. For validating frontmatter rather than markdown style, see Frontmatter schemas.

Default rules

The rules aren't stored in OK config — they live in your project's own native markdownlint file (see below). With no file, OK uses the same default as the VS Code markdownlint extension: every rule on, except MD013 (line-length). Nothing else is pre-disabled — so, exactly as in VS Code:

  • MD033 flags inline HTML, including MDX/JSX components like <Callout>,
  • MD041 flags docs that don't open with a top-level heading,
  • MD025 flags a frontmatter title beside an H1.

Turn off whichever don't fit your project — the first change creates the file for you.

Your .markdownlint.* file

The rules live in your project's own native markdownlint file — .markdownlint.jsonc, .markdownlint.json, .markdownlint.yaml, or .markdownlint.yml (.markdownlintrc is recognized too; the first that exists wins). It's a plain, portable markdownlint config: the same file markdownlint-cli2 and editor extensions read, with nothing OK-specific in it.

  • A file governs wholesale. When a native file exists it's honored exactly as markdownlint-cli2 would honor it — OK layers nothing underneath.
  • Per-folder cascade. The nearest .markdownlint.* file on the walk from a doc's folder up to the project root governs that doc, matching markdownlint-cli2 conventions. Subfolders inherit shared config explicitly via extends with a relative path (npm-package extends isn't resolved).
  • Aliases and severities. Rule aliases are valid keys (line-lengthMD013, matched case-insensitively), and "error" / "warning" severity strings are honored — either as the rule's value ("MD010": "error") or as a severity key in its options object. Everything else reports as a warning.
  • Executable configs. A .markdownlint.cjs / .mjs is detected but not executed: you get a loud configuration warning, linting falls back to OK's defaults, and OK will never rewrite the file. Convert it to JSON, JSONC, or YAML to have it govern.

The rule browser

Settings ▸ Plugins ▸ markdownlint (markdownlint appears there once the plugin is enabled) lists the full catalog — all 53 rules, generated from the installed engine's own config schema, so it always matches what actually runs. Search by id, alias, or name; browse by category (Headings, Lists, Whitespace, Code, Links & images, Style); or check Only modified to see just the rules your config changes. Expanding a rule reveals a link to its upstream documentation and typed editors for each of its options.

The same browser also opens directly on the config file. Open your project's root .markdownlint.json or .markdownlint.jsonc in the editor (reveal hidden files to find it) and use the Source / Rules toggle: Source, the default, shows the raw file read-only; Rules is the same no-code browser. Your choice is remembered separately from the document editor's own Visual/Markdown mode.

That toggle appears for JSON and JSONC configs only — a .markdownlint.yaml, .markdownlint.yml, or .markdownlintrc opens in the read-only file preview instead. And because rule edits target the project's governing root config, Rules is enabled only for that file; open a nested JSON config and Rules is disabled with a tooltip while Source still shows it.

Every edit writes back to your native .markdownlint.* file, and a rule you configured under an alias stays under that alias. JSON and JSONC files get minimal text edits, so comments and formatting survive; a YAML config is re-serialized, which drops its comments. Severity strings are shown as a read-only badge. If the project has no file yet, the first change creates a .markdownlint.json seeded with the defaults — from then on that file is the whole story for OK and every other markdownlint tool.

What a finding looks like

A markdownlint finding carries the rule id as its code under source markdownlint — rendered as markdownlint/MD010 in reports — with the upstream engine's message. In ok lint text output:

docs/guide.md
  7:5     warning  Hard tabs: Column: 5  markdownlint/MD010

And as a diagnostic in ok lint --json (or the MCP lint tool's structured content), where an auto-fixable rule also carries its fixes — the exact text edits --fix or the editor's Fix action would apply:

{
  "range": { "start": { "line": 6, "character": 4 }, "end": { "line": 6, "character": 5 } },
  "severity": "warning",
  "source": "markdownlint",
  "code": "MD010",
  "message": "Hard tabs: Column: 5",
  "fixes": [
    { "range": { "start": { "line": 6, "character": 4 }, "end": { "line": 6, "character": 5 } }, "newText": " " }
  ]
}

JSON ranges are 0-based and end-exclusive; the text report is 1-based. A rule that can't be auto-fixed simply has no fixes key. The shared report shapes — file grouping, counts, configuration warnings — are on the overview.

Auto-fix and severities

  • Auto-fix covers only some rules. markdownlint can mechanically fix problems like hard tabs, trailing whitespace, list-marker style, and blank-line spacing — but not ones that need a human decision, such as inline HTML (MD033) or a missing top-level heading (MD041). In source mode the Fix action appears only on a problem markdownlint can fix; the Problems panel offers Fix all to apply every fixable problem in the current scope at once; and ok lint --fix does the same across the whole project. Whatever's left is still reported, untouched — no affordance resolves the rules that need a human decision.
  • Severities. A rule reports as a warning unless your config promotes it to "error". Set "error" as the rule's value or via a severity key, then gate CI on just those rules with ok lint --errors-only.

See also