Skip to main content

mdlint-obsidian

PyPI version Python 3.10+ License: MIT Tests

A Python library and CLI tool that lints Obsidian Flavored Markdown files.

It checks for structural problems — unclosed wikilinks, invalid frontmatter, malformed tables, unmatched math delimiters and more — so you catch issues before they cause broken renders in your vault.


Installation

pip install mdlint-obsidian

Or with pipx for an isolated CLI install:

pipx install mdlint-obsidian

Usage

CLI

# Lint a single file
mdlint path/to/note.md

# Lint an entire vault (all .md files recursively)
mdlint path/to/vault/

# Enable broken-link checking (requires vault root)
mdlint note.md --vault path/to/vault/

# Show only errors (suppress warnings)
mdlint note.md --severity error

# Machine-readable JSON output
mdlint note.md --format json

Exit codes: 0 if no errors (warnings alone do not fail), 1 if any errors are found.

Example output (text format):

notes/my-note.md:5: [ERROR] unclosed-wikilink: Wikilink [[ is not closed with ]]
notes/my-note.md:12: [WARNING] broken-link: Link [[Missing Note]] does not resolve to an existing note

Example output (JSON format):

[
  {
    "file": "notes/my-note.md",
    "line": 5,
    "rule": "unclosed-wikilink",
    "severity": "error",
    "message": "Wikilink [[ is not closed with ]]"
  }
]

Python library

from mdlint_obsidian import validate, LintError, Severity

content = open("my-note.md").read()

# Basic validation
errors = validate(content)

# With vault path for broken-link checking
errors = validate(content, vault_path="/path/to/vault")

for error in errors:
    print(f"Line {error.line} [{error.severity.value.upper()}] {error.rule}: {error.message}")

The validate() function returns a list of LintError dataclass instances:

@dataclass
class LintError:
    rule: str          # e.g. "unclosed-wikilink"
    severity: Severity # Severity.ERROR or Severity.WARNING
    line: int          # 1-indexed line number
    message: str

Filtering by severity:

from mdlint_obsidian import validate, Severity

errors = validate(content)
errors_only = [e for e in errors if e.severity == Severity.ERROR]
warnings_only = [e for e in errors if e.severity == Severity.WARNING]

Rules

All rules skip content inside fenced code blocks (` `` or ~~~).

Frontmatter

Rule Severity Description
frontmatter-not-first ERROR A ---...--- block that parses as YAML frontmatter exists but does not start at line 1
frontmatter-invalid-yaml ERROR The frontmatter block cannot be parsed as valid YAML
frontmatter-unclosed ERROR An opening --- at line 1 has no closing ---

Wikilinks

Rule Severity Description
unclosed-wikilink ERROR [[ without a matching ]]
empty-wikilink ERROR [[]] with no content
wikilink-invalid-chars ERROR Wikilink target contains # or ^ in invalid positions (e.g. multiple #, or ^ before #)
broken-link WARNING [[Note Name]] does not resolve to an existing .md file in the vault (only when --vault is provided)

Embeds

Rule Severity Description
unclosed-embed ERROR ![[ without a matching ]]
empty-embed ERROR ![[]] with no content
embed-invalid-dimension ERROR ![[file|WxH]] where the dimension suffix is malformed (e.g. 300x or x200)

Callouts

Rule Severity Description
callout-invalid-type WARNING > [!type] where type is not one of the 13 built-in Obsidian types or their aliases (custom CSS types are valid, hence warning)
callout-missing-continuation ERROR The line immediately after a callout header is non-empty and does not start with >
callout-invalid-modifier ERROR The modifier after the callout type is not + or -

Built-in callout types: note, abstract/summary/tldr, info, todo, tip/hint/important, success/check/done, question/help/faq, warning/caution/attention, failure/fail/missing, danger/error, bug, example, quote/cite

Code Blocks

Rule Severity Description
unclosed-code-block ERROR An opening fence (``` or ~~~) has no matching closing fence

Formatting

Rule Severity Description
unclosed-highlight ERROR == opened but not closed on the same line
unclosed-comment ERROR %% opened but never closed (document-level)

Footnotes

Rule Severity Description
orphaned-footnote-ref ERROR [^id] reference in the body with no matching [^id]: definition
orphaned-footnote-def ERROR [^id]: definition with no matching [^id] reference in the body

Tables

Rule Severity Description
table-missing-separator ERROR Table header row is not followed by a separator row (`|---
table-inconsistent-columns ERROR A table body row has a different column count than the header

Math

Rule Severity Description
unclosed-math-block ERROR $$ block opened but never closed (document-level)
unclosed-inline-math WARNING $ opened but not closed on the same line (conservative: ignores $100-style currency)

Compatibility

Rule Severity Description
std-internal-link ERROR [text](path.md) or [text](path/note) — use [[wikilink]] instead
std-internal-image ERROR ![alt](local/path) — use ![[image.png]] instead
std-reference-link ERROR [text][ref] usage or [ref]: url definition — not supported in Obsidian
heading-no-space ERROR #Heading without a space after the # characters
indented-code-block ERROR 4+ space indented line preceded by a blank line — use a fenced ``` block instead
raw-html ERROR Raw HTML tags (e.g. <div>) — not reliably rendered in Obsidian
std-horizontal-rule ERROR *** or ___ horizontal rule — use --- instead

Development

git clone https://github.com/codeafix/mdlint-obsidian.git
cd mdlint-obsidian
pip install -e ".[dev]"
pytest
pytest --cov=mdlint_obsidian --cov-report=term-missing

See CONTRIBUTING.md for information on adding new rules.


License

MIT — see LICENSE.

Metadata

Release files for mdlint-obsidian 0.2.7

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for mdlint-obsidian 0.2.7
File Size Uploaded
mdlint_obsidian-0.2.7.tar.gz 25.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for mdlint-obsidian 0.2.7
File Interpreter ABI Platform
mdlint_obsidian-0.2.7-py3-none-any.whl Python 3 none any Details

Total release size: 49.1 kB

Release files / mdlint_obsidian-0.2.7.tar.gz

Download URL mdlint_obsidian-0.2.7.tar.gz
Size 25.6 kB
Tags Source
SHA-256 checksum
How to use checksums
1ad6d6f35ec1ca79f7c531f4798b3653c33b0e32e0e21df0ee466ae6cab5fa24
BLAKE2b-256 checksum
How to use checksums
a56446c27d8aa47c56dc5afd84d4ba358292458ef63e6c3cc2425032e3c3cf9b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on May 31, 2026.

Transparency log

Release files / mdlint_obsidian-0.2.7-py3-none-any.whl

Download URL mdlint_obsidian-0.2.7-py3-none-any.whl
Size 23.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
93007f29ba1cf15d67b97b17ad15012ad9022ff09d47045f9f88beadfd6b492b
BLAKE2b-256 checksum
How to use checksums
48e52bff9981417b60efc7bf8def05f0c717d36f4e902ad226fae5b7aaf161b5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on May 31, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.2.7 This release

2 release files

0.2.6

2 release files

0.2.5

2 release files

0.2.4

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page