mdlint-obsidian
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 |  — 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)
| File | Size | Uploaded | |
|---|---|---|---|
| mdlint_obsidian-0.2.7.tar.gz | 25.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| 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 logRelease 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