Michael Naumov1 downloadsLabel your links: give each link a relation, show the inverse relation from the other end, and browse every relation of a note in one pane.
A link in Obsidian says only that two notes are connected, never how. When Alice links to Bob, nothing tells you whether Bob is her child, her employer or a book she mentions, and from Bob's end the link is just another backlink. People write the answer down anyway, as child: "[[Bob]]" in the properties or employer:: [[Acme]] in the text, but the link itself never shows it.
Labelled Links reads that syntax as a labelled relation and shows the label where you meet the link: as a badge on the link, in a pane listing every relation of a note in both directions, and from the other end under its inverse label, so Alice's child link reads as parent on Bob. You declare each label pair once, in the settings, and nothing is ever written into your notes, so a vault stays readable without the plugin. Other plugins and scripts can query the same relations through a typed API.
It is meant to sit alongside Breadcrumbs, not replace it: Breadcrumbs builds hierarchies and their navigation, and this plugin labels the individual links.
The documentation is an interactive demo vault. Its notes explain how a link becomes a relation and how each end reads it, with buttons that open the relations pane, show the badges, change the registry and query the API.
Start reading here — it is plain markdown, so it works on GitHub with nothing installed.
A copy of the vault ships with every release. You can access it via any of the following:
Labelled Links: Open demo vault command.labelled-links-demo-vault.zip from the Releases. It unzips into a single labelled-links-demo-vault-<version> folder.demo-vault/ in this repository.parent <-> child, a symmetric spouse <-> spouse, or a lone label with no inverse. The inverse is worked out in memory and never written into a note. 02 SettingsLabelled Links publishes an API for querying a note's relations, and an event saying when they may have changed, for other plugins and scripts. Both are declared in api.d.ts, which is hand-written and imports nothing but obsidian, so you can depend on it without depending on anything of mine.
The event needs no API handle at all. Copy the declarations from api.d.ts into your own project, or hardcode the name, and subscribe:
this.registerEvent(
this.app.workspace.on('labelled-links:relations-changed', ({ paths }) => {
// `paths` names the notes whose relations may have changed, or is `null` when any note's may have.
console.log(paths);
})
);
For the relations themselves, take the API through the registry. The contract is 1.0.0 and moves independently of the plugin's own version, so ask for a range.
Beside api.d.ts, api-contract.ts holds the API's contract, LABELLED_LINKS_API_CONTRACT: for every method, a schema over its arguments and a schema over what it returns. It is the constant this plugin publishes with the API, so copy it next to api.d.ts and pass it as the contract, and both sides check the same definition. Besides ./api.d.ts and obsidian it imports from one package, @mnaoumov/typescript-lib, whose api-contract entry point carries the contract type, the Standard Schema builders and the common guards, so add that package to your project when you copy the file. (It is not named api.ts: TypeScript would read api.d.ts as that file's own declarations.)
import { watchPluginApi } from '@mnaoumov/obsidian-lib/plugin/plugin-api';
import type { LabelledLinksApi } from './api.d.ts';
import { LABELLED_LINKS_API_CONTRACT } from './api-contract.ts';
const labelledLinksApiRef = watchPluginApi<LabelledLinksApi>({
apiVersionRange: '^1.0.0',
app: this.app,
component: this,
contract: LABELLED_LINKS_API_CONTRACT,
pluginId: 'labelled-links'
});
// `value` is `null` while `Labelled Links` is not loaded, and becomes non-`null` on its own when it is.
const labelledLinksApi = labelledLinksApiRef.value;
if (labelledLinksApi) {
console.log(labelledLinksApi.getRelations('People/Alice.md', { direction: 'outgoing', label: 'child' }));
console.log(labelledLinksApi.getInverseLabel('child'));
}
A one-off lookup with no component to own a watch uses findPluginApi from the same module, with the same apiVersionRange, contract and pluginId. The index fills once the workspace layout is ready, so a query before that answers with no relations, and the event with paths: null says when it is filled. The Cross-plugin APIs guide covers the registry from the inside.
If you would rather not depend on @mnaoumov/obsidian-lib for that, the registry is a documented wire protocol you can read with nothing but obsidian — see Plugin API protocol.
The plugin is available in the official Community Plugins repository.
To install the latest beta release of this plugin (regardless if it is available in the official Community Plugins repository or not), follow these steps:
Add plugin button once and wait a few seconds for the plugin to install.By default, debug messages for this plugin are hidden.
To show them, run the following command in the DevTools Console:
window.DEBUG.enable('labelled-links');
For more details, refer to the documentation.
All notable changes to this project will be documented in the CHANGELOG.
Contributions are welcome — see CONTRIBUTING to get set up.
See my other Obsidian resources.