Michael Naumov113k downloadsCustomize attachment location with variables($filename, $data, etc) like Typora.
Paste a screenshot into Obsidian and it lands in one shared attachment folder under a name like Pasted image 20250101120000. A year later that folder holds a thousand files whose names say nothing about which note they belong to, and moving or renaming a note leaves its attachments behind. This plugin lets you decide where each attachment is stored and what it is called, from a pattern built out of tokens — ${noteFileName}, ${date:{momentJsFormat:'YYYYMMDD'}}, and about twenty more — and then keeps that arrangement true as notes are renamed, moved and deleted. The renaming and deleting half is handled by its companion plugin, Advanced Rename and Delete Handler, which this plugin requires: it does nothing until that plugin is installed, and installs it for you in one click.
The documentation is a demo vault. Every feature has a note that explains what it does and why you would want it, and walks you through it with a file you supply.
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:
obsidian-custom-attachment-location-demo-vault.zip from the Releases. It unzips into a single obsidian-custom-attachment-location-demo-vault-<version> folder.demo-vault/ in this repository.shouldFollowObsidianAttachmentLocation on, as a fresh install has it, and let Obsidian's own Default location for new attachments decide, keeping everything else this plugin does. Installing the plugin therefore moves nothing: attachments land where they always did until you switch that off and pick a pattern. While the plugin's pattern is in charge, Obsidian's own settings page says so instead of showing a value that is not in effect. 01 Attachment folder locationPasted image 20250101120000. 02 Attachment file namingrenameAttachmentsCreatedByOtherPluginsMode and those files are moved and renamed too, just after they appear — for every plugin, or only for the ones you name, or for every plugin except the ones you name. Off by default. 06 SettingsorphanAttachmentScanMode and the whole-vault sweep also reaches attachment folders whose note has been deleted, which nothing leads to any more. Off by default. 08 Delete unused attachmentsMoved to 09 Token reference — every token, its format schema and worked examples. For what a pattern is and the tokens most people use, start at 03 Tokens and patterns.
This heading stays so that the See available tokens links inside the plugin's own settings tab keep resolving, including from versions already installed.
Moved to 04 Custom tokens.
Moved to 06 Settings, under markdownUrlFormat.
Everything this plugin offers another plugin is declared in one hand-written file — api.d.ts at the repository root. It imports from obsidian and nothing else, so you can copy it into your own code or reference it where it sits, with no build-time dependency on this repository.
The API is published through the obsidian-dev-utils plugin registry under the plugin id obsidian-custom-attachment-location, so you get version negotiation, a handle that is revoked when this plugin unloads, and a wait that ends when it loads rather than a lookup that returns undefined because it ran first:
const apiRef = watchPluginApi<CustomAttachmentLocationApi>({
apiVersionRange: '^1',
app,
component: this,
pluginId: 'obsidian-custom-attachment-location'
});
const folder = await apiRef.value?.getAttachmentFolderPath({ notePath: 'Notes/Alpha.md' });
const properPath = await apiRef.value?.getProperAttachmentPath({ attachmentPathOrFile: 'image.png', notePath: 'Notes/Alpha.md' });
vault.getConfig('attachmentFolderPath')This plugin patches that call, which makes it look like the seam you want. It is not one. The patch answers with this plugin's value only while a note is open, and the value it answers with is the open note's — computed once when the file was opened. A plugin looping over every note in the vault therefore gets the active note's attachment folder for all of them, silently, with no error and nothing to distinguish it from a correct answer. The patch is a write-path override for Obsidian's own attachment creation, not a readable configuration.
getAttachmentFolderPath is the read, asked once per note. It answers null when this plugin leaves that note alone entirely, which is your cue to fall back to Obsidian's own attachmentFolderPath — a different answer from this plugin not being installed, and deliberately so.
Both reads arrived in contract version 1.0.0, and both are asynchronous: an attachment folder is the result of evaluating a user-written template whose tokens can read the note's frontmatter and the attachment's bytes, so there is no synchronous answer to hand back. Neither ever asks the user anything, so a whole-vault audit raises no dialogs.
A plugin that used to collect attachments itself hands its settings over through migrateSettings, added in contract version 1.1.0, so ask for '^1.1.0'. It proposes the values it held, and this plugin shows the user each one that would change next to the value it holds now. Nothing is written unless the user approves, and isApplied: false means they cancelled, so keep the proposal pending. api.d.ts lists the settings that can be proposed. The shape matches obsidian-dev-utils' SettingsMigrationApi, so its SettingsMigrationComponent can run the whole offer:
this.addChild(new SettingsMigrationComponent<MigratableCollectSettings>({
apiVersionRange: '^1.1.0',
app,
getProposedSettings: () => settingsComponent.settings.proposedCollectSettings,
pluginSettingsComponent: settingsComponent,
providerPluginId: 'obsidian-custom-attachment-location',
retireProposedSettings: () => settingsComponent.editAndSave((settings) => {
settings.proposedCollectSettings = null;
}),
sourcePluginId: this.manifest.id
}));
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('obsidian-custom-attachment-location');
For more details, refer to the documentation.
In Oct 2021, the plugin was created by RainCat1998.
From July 2024, the plugin is maintained by Michael Naumov.
From December 2025, the project repository is hosted at mnaoumov/obsidian-custom-attachment-location.
The original author's repository is preserved as an archive of issues/PRs/discussions/releases at RainCat1998/obsidian-custom-attachment-location.
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.
Copyright (c) RainCat1998, Michael Naumov.