Skip to main content
* orgheadingnetwork

This tool parses one or more [[https://gitlab.com/publicvoit/orgdown/][Orgdown]] files (= syntax of [[https://www.gnu.org/software/emacs/][Emacs]]
[[https://orgmode.org/][Org-mode]]), extracts headings with their metadata and inter-heading
links, classifies the link types by strength, and generates an
interactive HTML visualization of the resulting network.

You can also limit the visualization for a specific sub-hierarchy of
an Orgdown file.

Link types from weakest to strongest:

1. *Parent/Child* — implicit hierarchy from heading nesting
2. *Broken outgoing* — ID reference to a heading not found in the input files
3. *Uni-directional* — one heading links to another by ID
4. *Bi-directional* — two headings link to each other by ID

Only the strongest link type between any two headings is kept.

** Screenshots

Overview of the network visualization of my =notes.org= containing 14491 headings:

[[file:screenshots/2026-02-15T14.33.16 orgheadingnetwork of notes.org - Overview -- screenshots.png][file:screenshots/2026-02-15T14.33.16 orgheadingnetwork of notes.org - Overview -- screenshots preview.png]]

Selecting a node highlights its connections and shows details:

[[file:screenshots/2026-02-15T14.34.52 orgheadingnetwork of notes.org with Emacs node selected -- screenshots.png][file:screenshots/2026-02-15T14.34.52 orgheadingnetwork of notes.org with Emacs node selected -- screenshots preview.png]]

Using the link on the right hand side, you can navigate to linked headings and re-center the visualization there.

Searching for nodes shows a ranked dropdown list for quick navigation:

[[file:screenshots/2026-02-15T15.07.51 orgheadingnetwork with notes.org and search for Emacs -- screenshots.png][file:screenshots/2026-02-15T15.07.51 orgheadingnetwork with notes.org and search for Emacs -- screenshots preview.png]]

** Search

The search box in the top-left panel finds nodes by title, tags, ID,
or source file name. As you type, non-matching nodes are dimmed on the
canvas and a dropdown list shows up to 50 results ranked by relevance:

- Exact title match (highest)
- Title starts with the query
- Exact tag match
- Title contains the query
- Tag contains the query
- ID or file name contains the query (lowest)

Click any result to center the view on that node. You can also
navigate the list with *Arrow Up/Down*, press *Enter* to jump to the
highlighted entry, or *Escape* to clear the search.

** Recognized ID Sources

The tool extracts IDs from several places within each heading. The
following example demonstrates all of them:

#+begin_example
,* TODO Example heading with [[id:aaa-title-link][a link]] in the title
:PROPERTIES:
:ID: 1234-abcd-5678-efgh
:TRIGGER: ids("id:bbb-trigger-id1" "ccc-trigger-id2")
:BLOCKER: ids(ddd-blocker-id)
:END:

This body text contains a link [[id:eee-body-link][to another heading]]
and also a bare link id:fff-body-bare without description.

Even a plain id:ggg-plain-ref in running text is recognized, as well as
an ID hidden in a link description: [[https://example.com][see id:hhh-in-desc]].
#+end_example

IDs extracted from this heading:

| Source | Extracted ID | How |
|-----------------+-----------------------+------------------------------------------|
| Title | =aaa-title-link= | =[[id:...][desc]]= link in heading title |
| =:ID:= property | =1234-abcd-5678-efgh= | Becomes this heading's own ID |
| =:TRIGGER:= | =bbb-trigger-id1= | org-edna =ids("id:...")= syntax |
| =:TRIGGER:= | =ccc-trigger-id2= | org-edna =ids("...")= syntax (no prefix) |
| =:BLOCKER:= | =ddd-blocker-id= | org-edna =ids(...)= unquoted syntax |
| Body | =eee-body-link= | =[[id:...][desc]]= link in body |
| Body | =fff-body-bare= | =[[id:...]]= link without description |
| Body | =ggg-plain-ref= | Bare =id:...= reference in text |
| Body | =hhh-in-desc= | =id:...= inside a link description |

Headings without an =:ID:= property receive an auto-generated UUID
(prefixed =temp-=) so they can still participate in the hierarchy.

** Usage

Requires Python 3.13+ and [[https://docs.astral.sh/uv/][uv]].

#+begin_src bash
# Install dependencies
uv sync

# Basic: parse org files, produce org-network.html
uv run orgheadingnetwork.py file1.org file2.org

# Custom output path
uv run orgheadingnetwork.py -o my-network.html file.org

# Visualize only a sub-hierarchy (see below)
uv run orgheadingnetwork.py --subtree 2026-02-my-heading-id file.org

# Open result in default browser
uv run orgheadingnetwork.py --open file.org

# Dump the parsed data structure to stdout (for debugging)
uv run orgheadingnetwork.py --dump file.org

# Verbose logging
uv run orgheadingnetwork.py -v file.org

# Quiet (errors only)
uv run orgheadingnetwork.py -q file.org
#+end_src

The output HTML file is self-contained (loads D3.js from CDN) and can
be opened directly in a browser. It provides an interactive
force-directed graph with zoom, pan, search, link-type filtering, and
click-to-inspect.

Note: Broken outgoing links are hidden by default in the
visualization. Use the "Broken" checkbox in the link type filter to
show them.

You can use an Elisp function to invoke this tool as well. I wrote
=my-orgheadingnetwork-current-file()= which you can get from [[https://github.com/novoid/dot-emacs/blob/master/config.org][my Emacs
configuration]]. You'll need to adapt it to your paths and so forth.
With that function, it's really convenient to get a visualization - in
my case for the current Org-mode file.

There's also an Elisp function for visualizing the current sub-hierarchy:
=my-orgheadingnetwork-current-heading()=

** Subtree Mode

Use =--subtree ID= to visualize only the sub-hierarchy rooted at a
specific heading. This requires exactly one input file.

#+begin_src bash
uv run orgheadingnetwork.py --subtree 2026-02-my-heading-id file.org
#+end_src

The =ID= parameter must match the =:ID:= property value of a heading
in the file. An optional =id:= prefix is accepted and stripped, so
=--subtree foo-bar= and =--subtree id:foo-bar= are equivalent:

#+begin_example
,* My project heading
:PROPERTIES:
:ID: 2026-02-my-heading-id
:END:
#+end_example

The visualization will include:

- The heading matching the given ID (as the root of the sub-graph)
- All its descendants (children, grandchildren, etc.)

Links from subtree headings to headings outside the subtree (but still
in the same file) are treated as broken outgoing links. Uni-directional
incoming links from outside the subtree are ignored. Bi-directional
links where one end is inside the subtree are kept as broken outgoing
links from the subtree side.

The HTML title shows =Subtree "[heading title]" of "[filename]"= so
you can tell which mode produced the output.

Combining =--subtree= with multiple input files is not allowed and
produces an error.

** Author

Karl Voit, https://Karl-Voit.at/

** How to Thank Me

I'm glad you like my tools. If you want to support me:

- Send old-fashioned *postcard* per snailmail - I love personal feedback!
- see [[http://tinyurl.com/j6w8hyo][my address]]
- Send feature wishes or improvements as an issue on GitHub
- Create issues on GitHub for bugs
- Contribute merge requests for bug fixes
- Check out my other cool [[https://github.com/novoid][projects on GitHub]]

Metadata

Release files for orgheadingnetwork 2026.3.1.1

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

Source distribution (sdist)

Source distribution for orgheadingnetwork 2026.3.1.1
File Size Uploaded
orgheadingnetwork-2026.3.1.1.tar.gz 17.0 kB Details

Built distribution (wheel)

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

Total release size: 34.1 kB

Release files / orgheadingnetwork-2026.3.1.1.tar.gz

Download URL orgheadingnetwork-2026.3.1.1.tar.gz
Size 17.0 kB
Tags Source
SHA-256 checksum
How to use checksums
a66248295ae7f8670e09892c1aedf6f3a49c72a08d32e629a0633ab74bd29bad
BLAKE2b-256 checksum
How to use checksums
95bbe390ff997570b74d89b330d278129e708f7833d5872d33566295e57bac38
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.9.9 {"installer":{"name":"uv","version":"0.9.9"},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"13","id":"trixie","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / orgheadingnetwork-2026.3.1.1-py3-none-any.whl

Download URL orgheadingnetwork-2026.3.1.1-py3-none-any.whl
Size 17.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
27f8a1a5dfd270b5c29cb911fdc3ca9a05ecd929af26edd862edc78d4ad85fc5
BLAKE2b-256 checksum
How to use checksums
a527ba7b25fae68aa456c137d63a5b191be29d76ce010ab95da3acad25998c58
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.9.9 {"installer":{"name":"uv","version":"0.9.9"},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"13","id":"trixie","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release history Release notifications | RSS feed

This release

2026.3.1.1 This release

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