zou41 downloadsShow a real folder or note as a virtual subfolder anywhere in the file explorer. Files stay in one place, and both locations update together.
Show a real folder (or a single note) as a virtual subfolder anywhere in the file explorer — nothing is moved, copied, or symlinked.
Install · Usage · How it works · Compatibility · Limitations
Disk (the only real copy) Obsidian file explorer
03 References/Project A/ 01 In progress/
├── alpha.md ──────▶ ├── meeting-notes.md
├── beta.md └── Project A ↪ ← virtual mount
└── subfolder/ ├── alpha
├── beta
└── subfolder
The virtual entry looks and behaves like a native folder: click a note and the real file opens, edits show up in both places instantly, and adding or deleting files in the source folder is reflected in the mirror.
No folder is created on disk — the mapping lives in the plugin's own data.json.
In one sentence: the file exists once; the virtual location is just an extra node drawn into the file explorer. Rename, create and delete act directly on the real file.
| Feature | Details | |
|---|---|---|
| 📁 | Folder mirror | Mount a whole folder under any other folder; the tree follows the source live (create / delete / rename). |
| 📝 | Single-note mounts | Put one note in several places at once — every occurrence points to the same real file. |
| 🖱️ | Mount from the context menu | Right-click (long-press on mobile) any folder → Mount as virtual folder…, pick the target and edit the display name. |
| 🧩 | Native source menu | Right-click a virtual entry and get the same menu as the real folder or note: new note, new folder, rename, move, copy, delete. Every action operates on the source. |
| 🎨 | Native look | Reuses the core tree classes (tree-item, tree-item-self, tree-item-flair…), so themes and hover states apply. |
| 🔄 | Rename tracking | If you rename or move the source folder, the mount follows automatically. |
| 🪶 | Zero disk footprint | No directories, no symlinks, no junctions, no duplicated files. |
| 📱 | Desktop and mobile | Same code path; the long-press menu and clipboard fallback work in the mobile app. |
Option 1 — community plugins (recommended)
Settings → Community plugins → Browse → search “Virtual Mount” → Install and enable.
Option 2 — BRAT
Add ko-Ani/obsidian-virtual-mount as a beta plugin; updates can then be installed from inside Obsidian.
Option 3 — manual
main.js, manifest.json and styles.css from the latest release.<your vault>/.obsidian/plugins/virtual-mount/.Mount a folder
Mount as virtual folder….Right-clicking a note shows Mount as virtual note… instead.
The settings tab has the full editor (type, source, target, name), a list of mounts with per-mount enable toggles, and display options:
| Option | Effect |
|---|---|
| Folders before notes | Sorting inside a mount block only — blocks always sit at the top of the target folder. |
| Show the ↪ badge | Marks virtual entries so they're easy to tell apart from real ones. |
| Reveal real location on open | After opening a file from a virtual entry, scroll the native explorer to its real path. |
| Max items per virtual folder | Larger folders are truncated with a “N more” hint. |
Commands (Ctrl/Cmd+P):
Add virtual mount…Mount the current note to…Mount the current note's folder to…Refresh virtual mountsInteractions
| Action | Result |
|---|---|
| Click a virtual note | Opens the real file (Ctrl/Cmd+click or middle-click for a new tab). |
| Click a virtual folder | Collapse / expand. Subfolders start collapsed and load only when opened; expanded state is remembered, just like native folders. |
| Right-click / long-press a virtual entry | The native menu of its source folder or note: new note, new folder, rename, move, make a copy, delete… |
| Right-click / long-press a mount root | The same menu plus Reveal real location, Copy real path and Remove this mount. |
Obsidian's file index is built by scanning the vault: TFile.path is the real path, and no API can add a virtual path to that index. Obsidian also explicitly ignores intra-vault symlinks, because two copies of the same file would make [[wikilinks]] ambiguous (help).
So this plugin is a view layer: it reads real TFolder data and injects equivalent nodes into the native file explorer. The virtual path is visual only — the file always keeps its real path, which is exactly why edits stay in sync.
Obsidian 1.6+ renders the file explorer with a virtualized tree: setChildrenInPlace() replaces the children of .nav-folder-children on scroll, so anything appended there is wiped. This plugin therefore:
data-path on its title,.vm-children (same tree-item-children styling, but owned by the plugin),MutationObserver),click / auxclick / contextmenu so the core's delegated handlers never see virtual rows.The file-menu workspace event is used to add the right-click item, filtered to the file-explorer-context-menu source. For a virtual entry, the plugin hands the real TAbstractFile back to the file explorer view's own onFileContextMenu(), so the menu is generated by core with the exact source as its target; plugin items are appended through a one-shot file-menu listener. On recent Obsidian versions core anchors a menu to the real tree row and auto-closes it when that row is not visible, so the plugin temporarily hides that row association while the menu is built and restores it immediately afterwards. If that internal method is ever unavailable, a small built-in menu (reveal / copy path / remove mount) is used instead.
The mobile app runs the same bundle and the same tree DOM, so mounts render identically. Long-press opens the same context menu, and “Copy real path” falls back to execCommand where navigator.clipboard is unavailable.
isDesktopOnly is false.
| Item | Details |
|---|---|
| Minimum | Obsidian 1.6.0+ |
| Platforms | Desktop (Windows / macOS / Linux) and mobile (iOS / Android) |
| Tested | 1.14.5, plus earlier 1.8.x |
| Verification | Tree DOM and menu behaviour checked against the shipped obsidian.asar |
[[01 In progress/Project A/alpha]] will not resolve; links always use the real path.pnpm install
pnpm dev # watch build
pnpm build # typecheck + production bundle -> main.js
pnpm test # jsdom tests: DOM injection, plugin entry, i18n
The DOM tests replicate the real 1.8.x tree structure extracted from app.js, including the virtualized-children behaviour.
If it saved you a few file moves, a ⭐ is the best way to say thanks.
Changelog: CHANGELOG.md · 中文说明: README.zh.md