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)

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.

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.7.tar.gz (25.6 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.7-py3-none-any.whl (23.6 kB view details)

Uploaded Python 3

File details

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

File metadata

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

File hashes

Hashes for mdlint_obsidian-0.2.7.tar.gz
Algorithm Hash digest
SHA256 1ad6d6f35ec1ca79f7c531f4798b3653c33b0e32e0e21df0ee466ae6cab5fa24
MD5 7fd7027d6b928de9518ed86f016034a1
BLAKE2b-256 a56446c27d8aa47c56dc5afd84d4ba358292458ef63e6c3cc2425032e3c3cf9b

See more details on using hashes here.

Provenance

The following attestation bundles were made for mdlint_obsidian-0.2.7.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.7-py3-none-any.whl.

File metadata

  • Download URL: mdlint_obsidian-0.2.7-py3-none-any.whl
  • Upload date:
  • Size: 23.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for mdlint_obsidian-0.2.7-py3-none-any.whl
Algorithm Hash digest
SHA256 93007f29ba1cf15d67b97b17ad15012ad9022ff09d47045f9f88beadfd6b492b
MD5 55c4ae9427a827dec1b426fc7e7fef0f
BLAKE2b-256 48e52bff9981417b60efc7bf8def05f0c717d36f4e902ad226fae5b7aaf161b5

See more details on using hashes here.

Provenance

The following attestation bundles were made for mdlint_obsidian-0.2.7-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