Link Picker

approved

by Michael Naumov

Pick a note by navigating folders and insert a link to it, from a command or from your own scripts. - This plugin has not been manually reviewed by Obsidian staff.

241 downloadsUpdated 19d agoMIT

Link Picker

Buy Me a Coffee GitHub release GitHub downloads Coverage: 100%

Obsidian's own link autocomplete searches the whole vault and ranks it fuzzily, which is exactly wrong when you know the link belongs in one folder and you want the same note to come first every time. This plugin is a link picker you can point at a folder and then navigate — pick a folder to descend into it, pick .. to come back out — with a ranking that is deterministic rather than fuzzy: an exact name beats a prefix, which beats a path match, which beats a scattered word match.

It is also callable. The picker returns a string, so a template or a script can ask for a link and drop the answer straight into a property value.

Navigate folders instead of guessing names

More screenshots
One query, one order — exact match first Navigate folders with a tap — no hotkeys needed Exact match first, then prefix, then the rest

Demo vault

The documentation is a demo vault. Every feature has a note that explains what it does and how to try it.

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:

  1. Running the Link Picker: Open demo vault command.
  2. Downloading link-picker-demo-vault.zip from the Releases. It unzips into a single link-picker-demo-vault-<version> folder.
  3. Browsing its source in demo-vault/ in this repository.

What it does

  • Navigate instead of guessing — pick a folder to descend into it and .. to come back out, so the list is one folder's contents rather than the whole vault. 02 Navigating folders
  • A ranking you can predict — an exact name beats a prefix, which beats a path match, which beats a scattered one, so the same query puts the same note first every time. 03 Ranking
  • Aliases and folder notes are names too — a note aliased three ways offers three rows, and a folder's alias lives on its folder note, read from your existing folder-notes setup rather than configured again here. 03 Ranking
  • Named pickers — a folder you link into constantly becomes its own command, already rooted there, with its own prefix, suffix and placeholder. 04 Named pickers
  • Callable from a template or a script — the picker resolves with the link as a string, so the answer can drop straight into a property value instead of being typed at a cursor. 06 Calling it from a script

Picking a link

Run Link Picker: Insert link... in an editor. Any selected text seeds the query and is replaced by the link you choose.

Inside the picker, Enter on a folder descends into it and Enter on .. goes back up.

Everything else lives in the strip along the bottom, as a control you click and a hotkey you can press instead. Both routes run the same thing, and the controls are what make the picker usable on a phone, where there is no Alt key at all.

ControlKeyWhat it does
No linkAlt + 1Choose nothing, and insert an empty link
Create newShift + EnterCreate a note with the name you typed, and link to it
All filesAlt + 2Show all files, not only markdown
SubfoldersAlt + 3Include subfolder contents
Folders onlyAlt + 4Show only folders
By dateAlt + 5Lead with the most recently updated note (on by default)

A toggle that is on is filled in, so the strip says what the picker is currently doing. All files and Subfolders grey out while Folders only is on, since between them they would empty the list.

Ranking

With a query typed, items are ranked in tiers, and only within a tier does anything else break the tie:

  1. The name matches exactly
  2. The name starts with the query
  3. The path contains the query (only when the query itself contains a /)
  4. Every query term equals some part of the path
  5. Every query term starts some part of the path
  6. Every query term appears somewhere in the path
  7. Every query term's characters appear somewhere in the path in order — only when Segment matching is Fuzzy

Aliases count as names, so a note with three aliases offers three rows that rank separately. Inside a tier, folders come first; then recently opened files, then updated date, then shallower paths, then alphabetical.

The seventh tier is what Segment matching adds, and it is deliberately the weakest: turning it on changes what the picker finds, never the order it finds it in. Brv reaches Bravo without displacing anything you spelled out.

With no query typed, .. and the current folder's own folder note hold the top two rows, so navigating out never means scrolling.

Named pickers

If you link into one folder constantly, navigating there every time is the wrong shape. Add a picker in the settings and it becomes its own command, already rooted where you wanted to be — a vault that files people under People and courts under Legal gets two commands rather than one that asks first.

Each picker carries a name, a folder, whether it starts with subfolders included, a prefix and suffix, a placeholder, and whether it offers to create notes. The prefix and suffix wrap the link, so a prefix of "Person: " is what makes the result drop straight into a note's property list.

A picker's command is identified by an id minted when the picker is created, not by its name, so renaming a picker keeps whatever hotkey you bound to it.

Anything richer than those fields is code rather than configuration — validating a name, deriving a folder from it, seeding frontmatter. That is what the callable API is for.

Folder notes

The picker understands folder notes. A folder's note is not offered under its own file name — Foo/Foo.md says nothing that the Foo row did not already say, and choosing the folder is how you reach it. A folder note's aliases are still offered, so Foo/Foo.md aliased The Foo project is reachable under that name.

Where that note lives is read from the installed folder-notes plugin by default, so a vault that already has folder notes needs no configuration. Folder note location in the settings overrides it.

Settings

  • Pickers — the named, preconfigured pickers, each of which becomes its own command.
  • Folder note locationAuto (read the folder-notes plugin), inside the folder, beside the folder, or none.
  • Folder note name — what the note is called, when not on Auto.
  • Excluded paths — substrings; a path containing any of them is hidden. Point this at your attachment folder.
  • Segment matchingSubstring (the default: a query term must appear inside a path part as one unbroken run) or Fuzzy (its characters need only appear in order, so Brv finds Bravo). Finding more, never reordering.
  • Updated property — the frontmatter property holding a note's last-updated timestamp, used by the sort-by-updated ordering. Empty falls back to the file's modification time.
  • Title property — the frontmatter property holding a note's display title, used as the alias of a note the picker creates. Empty falls back to the file name.

API

The picker is callable, and that is where the plugin came from: it is an extraction of a script whose every consumer was a Templater template writing a link into a property value. Those callers want the string, not an edit at a cursor, so the API is not an extra bolted onto a command — it is the other half of the plugin.

It is published through obsidian-dev-utils' cross-plugin API registry, keyed by the plugin id, version-negotiated, and revoked automatically if the plugin is disabled. The contract version is 1.1.0, and it is independent of the plugin's own version.

import { watchPluginApi } from 'obsidian-dev-utils/obsidian/plugin/plugin-api';

interface LinkPickerApi {
  select(params: LinkPickerApiSelectParams): Promise<string>;
}

const ref = watchPluginApi<LinkPickerApi>({
  apiVersionRange: '^1',
  app: this.app,
  component: this,
  pluginId: 'link-picker'
});

// `ref.value` is `null` until the plugin has loaded, and becomes non-null on its own.
const api = await ref.whenAvailable();
const link = await api.select({ folderPath: 'People', prefix: 'Person: ' });
// → 'Person: [[Ada Lovelace|Ada]]'

select resolves with the link text and rejects when the picker is dismissed — dismissing is the caller's cue that the user backed out, which is different from the empty string that No link returns. Every option is optional; anything omitted falls back to the settings above.

  • createNote — called when the user picks Create new, and given the folder the picker is currently rooted at plus what they typed. This is the hook the whole API exists for: validating a name, deriving a subfolder from it, seeding frontmatter and applying a template are vault conventions, and none of them are expressible in settings. Without it the plugin creates an empty note.
  • folderPath — the folder the picker opens rooted at. A starting point, not a fence.
  • includeSubfolders — whether it starts with subfolder contents included.
  • initialQuery — seeds the input, so a picker opened over a selection starts filtered by it.
  • prefix and suffix — wrap the link. A prefix of "Person: " produces Person: [[Ada]]; plain strings rather than field names, so "- " or " work too.
  • shouldApplyPrefixSuffixWhenNoLinkSelected — whether the prefix and suffix are still emitted when the user presses No link. Off by default, so declining returns '' rather than a "Person: " with nothing after it.
  • placeholder — the modal's placeholder text.
  • shouldAllowCreate — whether Create new is offered at all.
  • sourcePathOrFile — the note the link is written INTO, which decides whether it comes out relative or absolute. Defaults to the active file, and worth passing explicitly when the note being written to is not the one Obsidian considers active — which is the case while a template renders a brand-new note.
  • excludedPathPatterns, folderNoteConfig, segmentMatchMode, titlePropertyName, updatedPropertyName — per-call overrides of the matching settings.

06 Calling it from a script in the demo vault has a runnable version of the above.

Installation

The plugin is available in the official Community Plugins repository.

Beta versions

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:

  1. Ensure you have the BRAT plugin installed and enabled.
  2. Click Install via BRAT.
  3. An Obsidian pop-up window should appear. In the window, click the Add plugin button once and wait a few seconds for the plugin to install.

Debugging

By default, debug messages for this plugin are hidden.

To show them, run the following command in the DevTools Console:

window.DEBUG.enable('link-picker');

For more details, refer to the documentation.

Changelog

All notable changes to this project will be documented in the CHANGELOG.

Contributing

Contributions are welcome — see CONTRIBUTING to get set up.

Support

Buy Me A Coffee

My other Obsidian resources

See my other Obsidian resources.

License

© Michael Naumov

For plugin developers

Search results and similarity scores are powered by semantic analysis of your plugin's README. If your plugin isn't appearing for searches you'd expect, try updating your README to clearly describe your plugin's purpose, features, and use cases.