Skip to main content

HyperMarkDown

The Python implementation, and the hmd command

PyPI Python versions CI Documentation License: MIT

HyperMarkDown (.hmd) is ordinary GitHub-flavored markdown plus the part HTML had on its first day: a link that means something, a page that can be made out of other pages, a document that is part of a web rather than a file in a folder. Every .md file is already valid .hmd, so a tree is adopted one rename at a time.

This distribution is the canonical implementation of that format. It ships three things:

  • hmd, a command line tool — lint a tree of cards, render one, dump the link graph.
  • the library under it — scanner, resolver, embed expander, and renderer, importable as hypermarkdown.
  • a MkDocs plugin, installed with the mkdocs extra, which builds a tree of cards into a website.

There is a second implementation in TypeScript for editors. Where the two disagree about a case the shared conformance corpus covers, this one defines the answer.

The format itself — every construct, the resolution algorithm, the rule IDs — is documented at hypermarkdown.org. This page is about the tool.

Install

pip install HyperMarkDown            # or: uv pip install HyperMarkDown
pip install "HyperMarkDown[mkdocs]"  # with the site builder

Python 3.11 through 3.14.

Sixty seconds

Point hmd at a tree of cards and lint it:

hmd lint --root path/to/your/wiki
glossary/index.hmd:11:3: warning[HMD001] [[idempotency]] does not resolve to a page

0 error(s), 1 warning(s)

Exit codes are pinned for CI: 0 clean, 1 diagnostics, 2 usage error. Add --strict to fail on warnings, --format json for machine-readable output.

That output is examples/small/, a runnable wiki that exercises the spine walk, both import forms, use inheritance, folder notes, and most of the syntax. It lints with zero errors and exactly one deliberate warning — the red link above, showing what an unwritten page looks like. The fixture travels in the repository rather than in the distribution, so try it from a clone:

git clone https://github.com/ewiger/hypermarkdown
hmd lint --root hypermarkdown/examples/small

The card it is reading

---
tags: [area/auth, status/accepted]
use: [autodiscovery]
import:
  - from /shared import tokens as shared-tokens
  - from /glossary import *
---

# Login

- `[[tokens]]` → the sibling card, found on the spine
- `[[shared-tokens]]` → `shared/tokens.hmd`, a named import under an alias
- `[[token]]` → `glossary/token.hmd`, via the imported search path

See [[shared-tokens#Rotation|the rotation window]], and embed one block rather
than restating it:

![[token#^definition]]

Six constructs are what the format adds — wikilinks, aliased links, heading links, block anchors, block references, and the three embed forms. Four frontmatter keys mean something to this tool: tags, use, import, and nav. Every other key is yours, and nothing will inspect it.

Commands

Command What it does
hmd init Write .hmd/config.toml and create the namespace root it names
hmd lint Parse, resolve, and report — HMD001–HMD017, text or JSON
hmd render Expand embeds and rewrite links, to flat markdown or HTML
hmd graph Dump the resolved link graph as JSON
hmd info Show the resolved root and discovery policy
hmd --version Print the installed version

Every command but init takes --root to override the namespace root, which otherwise comes from the wiki setting in .hmd/config.toml (defaulting to doc/wiki). A .hmd/ directory doubles as the project root marker, so any subtree can be self-contained.

hmd init                    # here; the namespace is named after the directory
hmd init path/to/project --wiki notes --name shared

init writes every setting at its default rather than leaving them implicit — the file is where you find out which keys exist. It creates the wiki root alongside the config, since a wiki setting pointing at nothing is an error every later command reports. It refuses to replace a config it did not write; --force overrides that.

The [namespace] section names this tree so another project can bind to it and address a card as name:card. --name defaults to the project directory's own name, repaired into that address form if it has to be — a folder called My Notes becomes My-Notes. A --name you pass is validated instead of repaired, because a name you typed is a request rather than a guess. Nothing reads either key yet: binding is specified and not implemented, so the section records intent.

hmd render --to markdown is one-way on purpose. Flattening a card erases the embed boundary and the provenance of every link, which is right for something you are shipping and wrong for something you are still editing.

The linter refuses to guess

A wiki that guesses is a wiki that quietly rots, because a link that silently changes meaning is indistinguishable from one that did not:

specs/auth/login.hmd:14:5: error[HMD002] [[tokens]] matches 2 pages; qualify it
  (candidates: shared/tokens.hmd, specs/auth/tokens.hmd)

The distinction it draws is a compiler's. A link to a page you have not written yet is a warning — writing forward is how a wiki grows, so it renders as a red link instead of breaking the build. A malformed link or multiple autodiscovery matches is an error. Ordered wildcard imports instead use declaration precedence and report shadowing as HMD016.

Resolution runs in phases and stops at the first hit: explicit imports, then the spine — this folder, then each folder above it, probed without recursion — then imported search paths, then one sweep of the whole tree. A bare name is a one-segment reference such as [[tokens]]; [[shared/tokens]] is an unqualified path, not a bare name. The spine never searches sideways, but if the earlier phases miss, autodiscovery may resolve a unique card elsewhere in the namespace. Use [[/shared/tokens]] to make that dependency explicit.

Publish it: a book with a wiki in it

The mkdocs extra registers a plugin through the mkdocs.plugins entry point. It registers .hmd files as pages, derives the nav from the namespace tree, expands embeds, and rewrites every wikilink to a source-relative link that MkDocs resolves and validates itself:

pip install "HyperMarkDown[mkdocs]"
mkdocs build --strict     # or: mkdocs serve

The namespace does not have to be the whole site, and a wiki on its own is not the interesting case — a book with a wiki inside it is. Point docs_dir at a documentation tree and root at the part of it that is a namespace: the book is hand-ordered markdown, the wiki is generated, and hmd://wiki says where the generated section belongs in your nav.

# mkdocs.yml
docs_dir: doc
plugins:
  - HyperMarkDown:
      root: doc/wiki        # only this subtree is a namespace
exclude_docs: |
  *.hmd

nav:
  - Home: index.md
  - Introduction: public/introduction.md
  - Wiki:
      - Overview: wiki/README.md
      - hmd://wiki          # ← the derived section lands here

An authored nav wins everywhere except where it names the wiki. Omit the placeholder and your nav is used verbatim; omit nav entirely and the whole thing is derived from the namespace tree. A card at a/b.hmd serves at a/b/, and so does a folder note at a/b/index.hmd — two names for one page, one URL. Unresolved links render red rather than failing the build, so a wiki stays publishable while it is still being written.

Publication is opt-in: nav: {visibility: public} is what puts a card on the site, it inherits down a subtree from a folder note, and the default is private. The failure mode is a missing page, never an unintended one.

hypermarkdown.org is that build, from this repository's own doc/ tree.

Math, diagrams, callouts

TeX mathematics, D2 diagrams, callouts, tables, task lists, footnotes, strikethrough. None of these are HyperMarkDown's own — they are the tier the wider markdown world settled on, and both hmd render --to html and the site build assume them present.

Diagrams go through the d2 binary, which is deliberately not a Python dependency: without it a diagram degrades to its own labelled source rather than failing the build.

The library

from pathlib import Path

from hypermarkdown import Workspace, config
from hypermarkdown.lint import check

workspace = Workspace(config.load(root_override=Path("doc/wiki")))
for diagnostic in check(workspace):
    print(diagnostic.path, diagnostic.rule, diagnostic.message)

parse, resolve, embed, urls, and lint do not import MkDocs — the plugin is one file, mkdocs_plugin.py, and swapping the renderer is that file plus a mkdocs.yml rather than a re-specification. Every entry point that ingests a document takes the document's text, not only a path, because the language server this package will grow serves buffers that have never been saved.

Versioning

Semantic, with the usual 0.x caveat made explicit: the format itself may change between minor versions. Until 1.0, treat a minor bump as potentially breaking for .hmd sources, not only for the Python API. What each release changed, and what is deliberately not implemented, is in CHANGELOG.md.

Development

This tool is tools/hmd/ in the HyperMarkDown monorepo, whose root is a uv workspace:

git clone https://github.com/ewiger/hypermarkdown
cd HyperMarkDown
uv sync --locked
uv run python -m pytest

Build the wheel with uv build --package HyperMarkDown — the --package flag is load-bearing, since a bare uv build at a workspace root produces an empty unknown-0.0.0 and exits zero.

  • tools/hmd/DEVELOP.md — this tool's own guide: the test loop, the gates, dependency policy, packaging, and how a release is cut.
  • DEVELOP.md — the repository's: the layout, how the documentation tree is organised, where progress is tracked, and how the site is published.

License

MIT — see LICENSE.

Release files for hypermarkdown 0.3.0

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

Source distribution (sdist)

Source distribution for hypermarkdown 0.3.0
File Size Uploaded
hypermarkdown-0.3.0.tar.gz 52.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for hypermarkdown 0.3.0
File Interpreter ABI Platform
hypermarkdown-0.3.0-py3-none-any.whl Python 3 none any Details

Total release size: 101.2 kB

Release files / hypermarkdown-0.3.0.tar.gz

Download URL hypermarkdown-0.3.0.tar.gz
Size 52.7 kB
Tags Source
SHA-256 checksum
How to use checksums
05458d7137567dfcae5c795bff2928fc722957eb17ed64b30e1ad17f002d48e0
BLAKE2b-256 checksum
How to use checksums
528bfc793decde13aa1adcfa5b2b48950a4afbeead81a80ed952802730053c80
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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 Sep 13, 2026.

Transparency log

Release files / hypermarkdown-0.3.0-py3-none-any.whl

Download URL hypermarkdown-0.3.0-py3-none-any.whl
Size 48.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
f58d8464a97c09242fbd969abcf3c8a992915798692c58abc152a13e19e62e11
BLAKE2b-256 checksum
How to use checksums
16b8b832fb9eda76f96da26910ef661f53d6c8b9f32351eab81b7c7dc3ee357a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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 Sep 13, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.3.0 This release

2 release files

0.2.0

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