Michael Naumov16 downloadsGive a note or an attachment a permanent key on request, and open it through a link that keeps working wherever the file moves.
A path is an address, not an identity. When a note moves, is renamed or grows into a folder, Obsidian updates every link inside the vault, but it cannot update the links that live outside it: obsidian://open?file=... links in bookmarks, messages, emails, task trackers and other vaults. Each of those breaks the first time the file moves.
Permanent Links gives a note or an attachment a permanent key, only when you ask for one, and copies a link that carries the key. The link keeps opening the file wherever it has moved since, and says so plainly when the file was deleted. A note carries its keys in a frontmatter property, readable by any tool, so nothing about the key is hidden in the plugin.
Ids that survive a move are a long-standing request: people in the Obsidian community have asked for them since at least 2021.
The documentation is an interactive demo vault. Every feature has a note that explains what it does and why you would want it, with a button that copies, opens, moves or deletes a supplied file so you can watch the link keep working.
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:
Permanent Links: Open demo vault command.permanent-links-demo-vault.zip from the Releases. It unzips into a single permanent-links-demo-vault-<version> folder.demo-vault/ in this repository.obsidian://permanent-links?vault=<vault>&key=<key>. The link opens the file wherever it is, at a heading or a block if you add one: see Permanent links.plan-2026, as an alias that keeps older links working; Remove key asks before removing one: see Permanent links.keys.json because an attachment has no frontmatter: see Permanent links.Note Composer merge offers to move the merged note's keys: see Key conflicts and manual edits.aliases, so the Quick Switcher and link suggestions find a note by its key, without touching the aliases you wrote: see Settings.Every setting is listed by its data.json key in Settings.
| Plugin | Features | How Permanent Links differs |
|---|---|---|
Add an ID to the front matter |
adds a globally unique id to the frontmatter of every note | Covers part of it: writes ids, but opens nothing by them; keys a file only when asked; keys attachments too |
Advanced URI |
obsidian://adv-uri links for many actions; opens a note by a frontmatter id field; can generate the id |
Same goal for links to notes: adds attachments; an index instead of a scan; duplicate repair; a record of deleted files; a warning when a key is edited by hand; forwarding to another vault |
Note UID Generator |
generates unique ids for notes, automatically or by command, into frontmatter | Covers part of it: writes ids, but opens nothing by them; keys attachments too |
Last searched for similar plugins on 2026-10-31.
This plugin publishes an API, declared in api.d.ts, for plugins that want to read a file's keys, resolve a key to a file, assign or add a key, or move keys from one file to another when they merge notes.
import { watchPluginApi } from '@mnaoumov/obsidian-lib/plugin/plugin-api';
import type { PermanentLinksApi } from './api.d.ts';
const apiRef = watchPluginApi<PermanentLinksApi>({
apiVersionRange: '^1.1.0',
app: this.app,
component: this,
pluginId: 'permanent-links'
});
// `value` is always current: `null` while this plugin is not loaded, and non-`null` on its own once it is.
const api = apiRef.value;
if (api) {
console.log(api.getKeys(file)); // ['project-plan', 'plan-2026']
}
The handle comes from the @mnaoumov/obsidian-lib plugin registry; Cross-plugin APIs covers watchPluginApi and the registry from the library's side. The contract version is 1.1.0, and it moves independently of the plugin's own version, so ask for a range.
If you would rather not depend on @mnaoumov/obsidian-lib for the handle, the registry is a documented wire protocol you can read directly — 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 (id: permanent-links) are hidden.
If you need to enable them 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.
Open issues through the issue forms, not as blank issues.
To report a bug from inside Obsidian, run the Permanent Links: Report a bug command. It opens the bug form with the plugin version, the Obsidian version and your operating system already filled in.
Filing from the CLI or the API (gh issue create --body, or an AI agent filing for you) skips the form, and gh issue create --template cannot load an issue form. In that case, open the matching form in .github/ISSUE_TEMPLATE/, copy every field as a ### <label> heading in the form's order, and fill in each one. Always state the plugin version, the Obsidian version and your operating system.