README file from
GithubImage Attachment Upload
Obsidian plugin: adds commands to pick an image on your device, upload it to Cloudflare R2, and drop it into the current note — either as an inline embed or as a frontmatter property.
Works on desktop and mobile (signing is done on-device with the Web Crypto API; no SDKs, no CORS setup needed on the bucket).
Commands
- Upload image and insert inline — opens a file picker, uploads to R2, inserts
at the cursor. - Upload image and set image property — opens a file picker, uploads to R2, sets the note's
imagefrontmatter property (name configurable) to the public URL.
Command line
With Obsidian's CLI enabled, image-upload:upload does the same job without a file picker, and writes the result straight into a note:
for f in ~/Photos/*.jpg; do
obsidian image-upload:upload src="$f" file="Dinner" append height=500
done
Use file=/path= with append or property to say where the URL should end up. Without them the upload still happens, but the URL is currently lost — Obsidian stops waiting for a CLI handler after a few tens of milliseconds, well before an upload finishes, so the returned URL never reaches stdout and can't be piped or captured. Failures do get reported, so a silent run is a successful one.
| Flag | Meaning |
|---|---|
src=<path> |
Image to upload — vault-relative, or an absolute path. Required. |
max=<px> |
Longest edge to downscale to, overriding the setting. 0 uploads full size. |
quality=<1-100> |
Encoder quality, overriding the setting. |
file=<name> / path=<path> |
Note to write into, resolved like other Obsidian CLI commands. |
append |
Append the embed to that note. |
property |
Set that note's image property to the URL. |
alt=<text> |
Alt text for the embed. Defaults to the note name. |
width=<px> |
Display width for the embed. |
height=<px> |
Display height; the width follows from the image's aspect ratio. |
height= is the useful one for a batch of mixed portrait and landscape shots — every image ends up the same height down the note.
Resizing
Images are downscaled so their longest edge fits Maximum image size (1600px by default) and re-encoded at Image quality (85). This applies to the commands and the CLI alike. A 12MP phone photo typically lands around a tenth of its original file size, with enough resolution left for a HiDPI screen. Set the maximum to 0 to upload untouched.
JPEG, PNG, and WebP are resized; anything else uploads byte-for-byte. PNGs stay lossless, so quality doesn't apply to them. Resizing happens on a canvas, which bakes in EXIF rotation and drops the remaining metadata. If an image can't be decoded it's uploaded as-is rather than failing.
Setup
- Create an R2 API token (Object Read & Write, ideally scoped to one bucket) — this gives you an access key ID + secret.
- Serve the bucket publicly via a custom domain (or the bucket's r2.dev development URL).
- Fill in the plugin settings: endpoint (
https://<account-id>.r2.cloudflarestorage.com), bucket name, path template, and the public base URL. - Enter the access key ID and secret access key, or turn on Store credentials in the keychain first and select Link on each to pick an existing keychain entry or add a new one.
Object keys follow the path template (default /{year}/{mon}/{day}/{filename}), with a short random suffix added to the filename to avoid collisions.
Where the credentials are kept
Store credentials in the keychain picks between the two options:
- Off (default). The credentials sit in plaintext in the plugin's
data.jsoninside your vault, as most uploader plugins do. They travel with the vault, so setup happens once. Scope the R2 token to the one bucket, and keepdata.jsonout of any git repo your vault lives in. - On. The credentials go into Obsidian's keychain, encrypted by the OS (macOS Keychain, Windows DPAPI, or a Linux secret store such as gnome-keyring/kwallet), and
data.jsonholds only the name of the entry. Turning this on clears the plaintext copy. The keychain is per-device and isn't synced, so you link the credentials again on each device — and if a device's secret store isn't working, uploads there fail until you link them or switch back to plaintext.
Installing via BRAT
Add rocketBANG/obsidian-image-attachment-upload as a beta plugin in BRAT.
Manually installing the plugin
Copy main.js and manifest.json into <vault>/.obsidian/plugins/image-attachment-upload/.
Development
Requires NodeJS v22.13+ and pnpm 11.
pnpm ito install dependencies.pnpm run devto start compilation in watch mode (producesmain.js).pnpm run buildfor a production build.pnpm run lintto lint.
Releasing
- Run
pnpm version patch(orminor/major) after updatingminAppVersionmanually inmanifest.jsonif needed — this bumpsmanifest.json,package.json, andversions.json. - Push a tag matching the exact version number (no
vprefix); the release workflow builds the plugin and drafts a GitHub release withmain.jsandmanifest.jsonattached.