Skip to main content

A linter for Obsidian Flavored Markdown files

Project description

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)

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.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

mdlint_obsidian-0.2.6.tar.gz (25.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

mdlint_obsidian-0.2.6-py3-none-any.whl (23.4 kB view details)

Uploaded Python 3

File details

Details for the file mdlint_obsidian-0.2.6.tar.gz.

File metadata

  • Download URL: mdlint_obsidian-0.2.6.tar.gz
  • Upload date:
  • Size: 25.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for mdlint_obsidian-0.2.6.tar.gz
Algorithm Hash digest
SHA256 d22f352f06d3ae45f9b0e79fe16fc9414279b811fa26a0c06e8b62538b3970fb
MD5 9b65b7ddb45149fc74492623ae4d5dfd
BLAKE2b-256 5305ece5c6ac1c76101a4fdff36f7f2bdbb1f589c107b3757b521f91f19ad469

See more details on using hashes here.

Provenance

The following attestation bundles were made for mdlint_obsidian-0.2.6.tar.gz:

Publisher: python-publish.yml on codeafix/mdlint-obsidian

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file mdlint_obsidian-0.2.6-py3-none-any.whl.

File metadata

File hashes

Hashes for mdlint_obsidian-0.2.6-py3-none-any.whl
Algorithm Hash digest
SHA256 983ae246324f8c06964bb314f91af53aa7210bbaab2f8b20f7f48d3eee910511
MD5 b5e2b68dddd00f431bf70553ff050d42
BLAKE2b-256 73b638fc79b263bf3681e5116089fdcb69deebd3e6b267c702a73e56f1ec2230

See more details on using hashes here.

Provenance

The following attestation bundles were made for mdlint_obsidian-0.2.6-py3-none-any.whl:

Publisher: python-publish.yml on codeafix/mdlint-obsidian

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page