max-fluff14 downloadsAuto-link unlinked mentions in your notes to headings in your glossary files, in any word form: plurals, declensions, verb endings, with stemming for six languages. Convert them to real wiki links.
Finds words in your notes and turns them into links to matching headings inside files you nominate as glossaries — in any word form (declensions, plurals), not just exact spellings. Keep a Guide.md with a ## Projectile heading, and every "projectile", "projectiles" or other form elsewhere gets highlighted and can become [[Guide#Projectile|projectiles]]: a real link that opens the file at that heading, with the note's own wording kept as the visible text.
It's the file-based sibling of Glossary Linker — that one treats each note as a term, this one treats each heading inside a chosen file as a term, on the same matching engine.
Available in the Obsidian community catalog: community.obsidian.md/plugins/heading-linker.
The plugin ships as main.js, manifest.json and styles.css. Eight language modules are baked into main.js, so morphology works the moment you install it. main.js is built from src/ with esbuild (see Development).
Words that match a heading are underlined in Reading view and in the editor (Live Preview / Source). Matching follows word forms through a stemmer, so "spawns", "spawning" and "spawn" all find a Spawn heading, and Russian "рой", "роя", "рои" all find Рой — not just the exact spelling. Editor highlighting can run live, on save, or off.
Compared with virtual-link plugins such as Virtual Linker and FakeLink, the two differences are that Heading Linker matches inflected forms (they match literal text) and that it can turn matches into real links, not only show an overlay.
The highlight is a live overlay that changes nothing on disk — but you can materialize it: turn matches into actual [[File#Heading|word]] wikilinks, for the current note, a selection, or every note in scope. A preview lists every replacement first; nothing is written until you apply, and a note edited since the preview is skipped. Materialized links are ordinary wikilinks, so they get native hover-preview and click, and count in the graph and backlinks.
Run it across the vault and the preview groups the replacements by file:
The reverse of materialize: replace heading links with their plain text again, for the current note, a selection, or all notes — also with a preview. Right-clicking a single heading link offers Unlink this link.
A heading can carry extra wordings — abbreviations or synonyms the stemmer won't reach — in an Obsidian comment right under it:
## Central nervous system
%% alias: CNS, brain and spinal cord %%
Now "CNS" and "brain and spinal cord" link to that heading too, in any word form, and appear in the autocomplete. Comments are invisible in Reading view, so the note stays clean; use alias: or aliases:, comma-separated. Reading them is cheap — see Performance — and the Heading aliases setting turns it off entirely if you don't use them.
With Suggest links while typing on, typing a word that is (a form of) a heading or an alias offers to complete it into a link. Off by default.
The same heading text in two different files is two different terms. When a word could point at either, the highlight is marked as ambiguous and asks which one you meant — on hover, on click, and in the materialize preview (one choice per word, applied everywhere).
Since the rows would otherwise read as the same heading twice, each carries a second line saying what it is and which note it lives in — that file is usually the only thing telling them apart. A row you reached through an alias rather than the heading's own text says so too, which answers the "why is this even on the list" question when the wording you clicked doesn't appear in the heading at all.
Two separate questions, two separate settings:
heading-linker: false frontmatter property.Both lists are editable from the settings tab, the file explorer's right-click menu, and the command palette (acting on the active note).
Matching reduces each word to a stem so different forms of the same word collapse together. Eight languages are built in — English, Russian, Ukrainian, German, Spanish, French, Latin and Greek — and you choose which are active and in what priority order. On first run the plugin enables English plus your Obsidian interface language, if a module exists for it. Latin and Greek stay off by default and are aimed at scholarly notes that quote classical terms; English on its own already links the common latinised plurals (indices/index, phenomena/phenomenon).
Enable only the languages your vault actually uses: since same-script languages combine, leaving German on in an English-only vault can occasionally over-stem a word. The Match mode setting also offers a lighter ending-strip or an exact (case-insensitive) mode instead of the full stemmer.
| Command | What it does |
|---|---|
| Link headings: this note / selection / all notes | Turn matches into links, with a preview. |
| Unlink headings: this note / selection / all notes | Revert heading links to plain text, with a preview. |
| Rebuild heading index | Re-scan the glossary files for headings and aliases. |
Plus per-note toggles that mirror the explorer menu, each shown only when it applies: Add / Remove this note … heading sources, Ignore / Stop ignoring this note as a heading source, Never link in this note / Stop always-excluding, Include this note in scope / Remove from scope.
Sources
| Setting | Default | Description |
|---|---|---|
| Where headings come from | Selected files and folders |
collect headings from the whole vault, or only from the files and folders you list |
| Ignored sources | — | files and folders whose headings are never indexed, even inside a chosen source |
| Heading levels | H1–H6 | which levels become terms |
| Read alias comments | on | pick up %% alias: … %% lines under a heading as extra wordings for it |
Scope
| Setting | Default | Description |
|---|---|---|
| Where to link | The whole vault |
link everywhere, or only inside chosen folders |
| Always-excluded folders | — | never link here, whatever the scope says |
Matching
| Setting | Default | Description |
|---|---|---|
| Match mode | Stemmer |
how an inflected word is matched: Stemmer reduces words to a root (recommended); Ending strip only chops common endings; Exact needs the exact spelling |
| Minimum heading length | 2 |
ignore headings and aliases shorter than this, so single letters don't match everywhere |
| Smart case for acronyms | on | a heading written mostly in capitals ("IT", "NASA") only matches text spelled the same way, so it leaves the ordinary word alone. Decided per form: an acronym alias stays case-sensitive even when its heading is an everyday word |
| Languages | English + interface language | per-language toggle; reorder with ↑↓ to set priority when same-script languages overlap |
| Link first occurrence only | off | link only the first occurrence of each heading per page |
| Excluded headings | — | heading texts that never become terms |
Highlighting
| Setting | Default | Description |
|---|---|---|
| Highlight in Reading view | on | draw the highlight in rendered notes |
| Highlight in the editor | Live |
Live, On save, or Off |
| Skip headings | on | don't link inside a note's own headings |
| Status bar count | on | show how many headings the open note matches |
| Count existing links too | on | include headings you have already linked in that count |
Autocomplete
| Setting | Default | Description |
|---|---|---|
| Suggest links while typing | off | offer a link to a matching heading as you type in an in-scope note |
| Minimum typed length | 3 |
how many characters to type before suggestions appear |
| Skip after characters | @#$^ |
stay quiet when the word follows one of these, so tags, math and other plugins' autocompletes keep their slot |
| Insert plain text | off | pick a suggestion and get the word alone instead of a link — the completion without the brackets |
Context menu — a toggle per group of right-click items: turn into link, open, exclude, unlink, and collect aliases from existing links.
Maintenance — the priority order (below) and rebuilding the index on demand.
Install more than one linker and they will sometimes claim the same word or the same link. It goes to whichever sits highest in Settings → Maintenance → Priority among linker plugins, and the loser stands aside — no double highlight, one entry in the right-click menu, one merged list of suggestions while you type.
The list appears only when another linker is installed. Each plugin moves itself, so reordering may take a move from more than one settings tab; every arrangement is reachable that way.
The highlight color and underline styles are exposed through Style Settings if you have it.
Words are never linked (and suggestions never fire) inside code blocks (``` and ~~~), inline code, frontmatter, %% comments, existing [[...]] and [..](..) links, or URLs; a note's own headings are skipped too unless you turn that off. When a link is written into a Markdown table cell, the alias pipe is escaped so the row isn't broken. Headings that contain |, #, [, ] or ^ are not indexed, because those characters can't sit inside a [[File#Heading]] target.
Rebuilding the index never reads file bodies — it works from Obsidian's metadata cache. Alias comments are the one thing that needs the body; they are read once per file, cached, and re-read only when that file changes, so a rebuild triggered by a settings change costs nothing extra. In whole-vault sourcing you can turn alias reading off completely. The per-keystroke check that suppresses suggestions in code, links and comments tests only the cursor position, not the whole document.
Most bundled language modules port well-known, permissively-licensed stemming algorithms (uk.js and el.js are the plugin's own, under its MIT license). All are free for commercial and non-commercial use; the only obligation is keeping the attribution notices, which are already in each file's header.
| Module | Algorithm | License | Reference |
|---|---|---|---|
ru.js |
Snowball Russian stemmer (Porter framework) | BSD (© 2001–2006 M. Porter & R. Boulton) | snowballstem.org · license |
uk.js |
Light suffix stemmer with vowel alternation | MIT (this plugin) | — |
en.js |
Porter stemmer (M. F. Porter, 1980) | Free use, released by the author | tartarus.org |
es.js |
Apache Lucene SpanishLightStemmer (UniNE, J. Savoy) |
Apache License 2.0 | source |
de.js |
Apache Lucene GermanLightStemmer (UniNE, J. Savoy) |
Apache License 2.0 | source |
fr.js |
Apache Lucene FrenchLightStemmer (UniNE, J. Savoy) |
Apache License 2.0 | source |
la.js |
Schinke Latin stemmer (Schinke, Greengrass, Robertson & Willett, 1996) | Own implementation, MIT (this plugin) | snowballstem.org |
el.js |
Light suffix stemmer with polytonic diacritic folding | MIT (this plugin) | — |
The es/de/fr stemmers were translated to JavaScript and adapted to this plugin's module interface; per the Apache License the source files note that they are modified ports. Apache 2.0 full text: https://www.apache.org/licenses/LICENSE-2.0. Heading Linker itself is released under the MIT license — see LICENSE.
The core is written as small CommonJS modules in src/ and bundled into main.js by esbuild. The language modules live in the shared submodule, under src/shared/morphology/languages/, and are bundled in through src/shared/morphology/builtin-languages.js; adding a language means contributing a module there and rebuilding (see languages/README.md). Nothing is loaded or executed at runtime.
Generic code shared with the sibling linker plugins lives in src/shared/, a git submodule of obsidian-linker-shared. Clone with --recurse-submodules so the build can find it:
git clone --recurse-submodules https://github.com/max-fluff/obsidian-heading-linker
npm install # once, installs esbuild
npm run build # bundle src/ -> main.js
In an existing clone without the submodule, run git submodule update --init first.
src/ layout:
main.js — the Plugin class: lifecycle, commands, menus, scope and sources, link writing, alias parsing, small helpers; applies the mixins below.constants.js — default settings.matcher.js — the heading index and matching engine (keysFor, tokenizeForm, rebuildIndex, findMatches, protected ranges).highlight.js — Reading-view DOM highlighting and the CM6 editor extension.materialize.js — turning matches into links and reverting them, plus the link context menu.modals.js — the materialize/unlink preview dialogs and the choose-heading dialog.settings-tab.js — the settings UI.folder-suggest.js — path autocomplete for the source/scope lists (feature-detected).heading-suggest.js — the editor autocomplete (EditorSuggest, feature-detected).shared/ — git submodule shared with the sibling plugins: markdown helpers, the i18n engine, the folder-list settings editor, and morphology/ (the language modules, their contract and validateLanguage()).locales/ — interface strings (English and Russian), fed to the shared i18n engine.main.js is generated; edit src/ and rebuild rather than editing it directly. node_modules/, package-lock.json and esbuild.local.mjs are git-ignored.
From Obsidian (recommended). Open Settings → Community plugins → Browse, search for Heading Linker, then Install and Enable. You can also open its catalog page directly: community.obsidian.md/plugins/heading-linker.
Manually. Download main.js, manifest.json and styles.css from the latest release into <vault>/.obsidian/plugins/heading-linker/, then enable the plugin in Settings → Community plugins.
Beta builds via BRAT. Add the repository max-fluff/obsidian-heading-linker to test unreleased changes before they reach the catalog.
Once installed, set your glossary files (or switch to whole-vault sourcing) under Settings → Heading Linker → Heading sources.
Requires Obsidian 1.4.0 or newer, and works on both desktop and mobile — it reads headings from Obsidian's metadata cache, not the filesystem. Interface in English and Russian, following Obsidian's language.
Nothing below is required, but the plugin cooperates with them if you have them:
Also by the author — the rest of the linker family. Two of them highlight words already in your notes and link them; two autocomplete a name into a deep-link that lands on the exact spot.
Glossary Linker — highlights glossary terms in any word form, turns them into real links, and learns new aliases from links you've already made. This plugin is its file-based counterpart: a heading as a term instead of a whole note. Works on desktop and mobile.
Code Linker — autocompletes references to your source code and inserts a deep-link that opens the file at the exact line in your editor (VS Code, JetBrains, …). Desktop-only.
Reference Linker — autocompletes links to external documents (PDF, Office, images) and inserts a deep-link that opens them at the right page in an external viewer. Desktop-only.
MIT, see LICENSE. Bundled third-party notices are in THIRD_PARTY_NOTICES.md.