Upload local note images to Cloudflare R2 and replace their links with public URLs.
Cloudflare R2 Sync moves local images out of your vault and into Cloudflare R2. When you run a sync, the plugin finds local image references in the active note, uploads them to R2, replaces the links with public URLs, and moves the local files to Obsidian trash.
Key features:
png, jpeg, jpg, and bmp to WebP before upload.Supported image reference formats:
![[path/to/image.png]]![[path/to/image.png|alias]]Remote http:// and https:// URLs are skipped automatically.
1.11.4 or later (desktop only)obsidian), leave location as Automatic and storage class as Standard.The plugin authenticates with S3-compatible credentials, not a Cloudflare API token.
Where to find your Account ID: Dashboard → R2 overview, or Workers & Pages → Overview. It is a 32-character hex string. See Find account and zone IDs.
The plugin sends PUT and DELETE requests directly to the R2 S3 API from Obsidian desktop (origin app://obsidian.md). Without a CORS policy the bucket rejects these requests, and every upload or delete fails silently.
[
{
"AllowedOrigins": ["app://obsidian.md"],
"AllowedMethods": ["PUT", "DELETE"],
"AllowedHeaders": ["*"]
}
]
Before moving on, have these five values ready:
| Value | Where to find it |
|---|---|
| Account ID | R2 overview or Workers & Pages overview |
| Bucket name | The name you chose |
| Public base URL | Bucket Settings → public URL |
| Access Key ID | Copied when creating the access key |
| Secret Access Key | Copied when creating the access key |
The plugin never stores raw credentials in its settings. Instead it reads them from Obsidian's built-in secret storage. Create two secrets before configuring the plugin:
r2-access-key-id.r2-secret-access-key.You will pick these names in the plugin settings. The plugin never touches the secret values directly.
Open Settings → Community plugins → Cloudflare R2 Sync.
| Setting | Description |
|---|---|
| Account ID | Your 32-character Cloudflare account ID. |
| Bucket name | The R2 bucket that receives uploaded images. |
| Public base URL | URL prefix used in replaced links, for example https://images.example.com. |
| Object key template | R2 object path pattern for article images (see Upload paths). Default: {year}/{month}/{timestamp}-{filename}. |
| Cover object key template | Optional separate path pattern for cover uploads. Leave empty to reuse the article template. |
| Cover frontmatter property | YAML key where Upload cover image writes the public URL. Default: cover. |
| Upload cache control | Cache-Control applied to every uploaded object. Default: 1 year, immutable. |
| Access key ID secret | Name of the Obsidian secret containing the R2 access key ID. |
| Secret access key secret | Name of the Obsidian secret containing the R2 secret access key. |
| Setting | Default | Description |
|---|---|---|
| Auto-upload on drop | On | Upload images to R2 when dropped into the editor. Disable for manual-only workflow. |
| Sync preview before upload | Off | Show a preview modal before any upload so you can review object keys and URLs, then select which images to upload. |
| Setting | Default | Description |
|---|---|---|
| Convert article images to webp | On | Re-encode png, jpeg, jpg, bmp to WebP for sync and drop uploads. Other formats upload unchanged. |
| Webp quality (article images) | 0.8 | Quality slider (0.5–1). Visible only when conversion is on. |
| Convert cover images to webp | Off | Re-encode raster formats to WebP on cover upload. |
| Webp quality (cover images) | 0.8 | Quality slider (0.5–1). Visible only when conversion is on. |
| Setting | Default | Description |
|---|---|---|
| Detailed error notices | Off | Show extra notices with error category, HTTP status, and a short hint after a failure. Useful for support screenshots. |
 links appear at the drop position.Example result notice:
Image sync: 3 uploaded, 1 skipped, 0 failed, 3 trashed, 0 trash failed.
Successfully uploaded images have their local links replaced with public R2 URLs. The original local files are moved to Obsidian trash (not permanently deleted). If a local file has multiple references in the note, it uploads once and all references are replaced. Wiki embeds are converted to Markdown links (for example ![[image.png|alias]] becomes ).
png, jpeg, jpg, bmp, gif, ico, webp, svg).The delete command finds every reference whose URL starts with Public base URL, including:
 and angle-bracket variants.Both article images and cover images use the same placeholder rules but can have separate templates.
| Group | Placeholders |
|---|---|
| Date and time | {year}, {month}, {day}, {hour}, {minute}, {second}, {timestamp} |
| File | {filename} |
| Note | {slug}, {notepath} |
| Upload | {hash}, {uuid} |
{timestamp} — compact local time: YYYYMMDDHHmmss.{filename} — original file name normalized to lowercase, safe characters, with the final extension set to .webp when conversion is on.{slug} — active note file name without extension, normalized (for example my-post from My Post.md).{notepath} — vault-relative note path without extension, each segment normalized (for example blog/my-post from blog/My Post.md).{hash} — first 12 hex characters of the SHA-256 hash of the uploaded bytes. Computed only when present in the template.{uuid} — a new UUID v4 (no hyphens) per upload. Generated only when present in the template.When the active note cannot be resolved, {slug} and {notepath} fall back to untitled.
Object key template)Default: {year}/{month}/{timestamp}-{filename}
Example — syncing My Screenshot.png on 15 Apr 2026 at 14:30:22 with Convert article images to webp on:
2026/04/20260415143022-my-screenshot.webp
Example with note blog/My Post.md and template {slug}/{filename}:
my-post/20260415143022-my-screenshot.webp
Cover object key template)Leave empty to reuse the article template. To give covers their own prefix:
| Setting | Value |
|---|---|
| Object key template | {year}/{month}/{timestamp}-{filename} |
| Cover object key template | cover/{year}/{month}/{timestamp}-{filename} |
Uploading hero.png on 15 Apr 2026 at 14:30:22 with Convert cover images to webp on:
cover/2026/04/20260415143022-hero.webp
Leading slashes in templates are normalized away.
The repository uses mise to manage the Node.js version (mise.toml pins Node 24) and pnpm as the package manager (pinned in package.json).
# Install mise (if not already installed), then let it set up Node:
mise install
# Install dependencies and run all checks:
pnpm install
pnpm check # TypeScript, ESLint, production bundle
pnpm build runs typecheck and esbuild. pnpm dev runs the esbuild watcher.
| Area | Files |
|---|---|
| R2 connection | pluginR2.ts, r2.ts |
| URLs and keys | publicR2Url.ts, objectKeyTemplate.ts |
| Images | imagePaths.ts, imageContentType.ts, convert.ts, droppedImageFiles.ts |
| Markdown / vault | noteBodyImageRefs.ts, noteMarkdownR2PublicLinks.ts |
| Features | syncActiveNoteImages.ts, editorDropUpload.ts, cover.ts, deleteActiveNoteR2Images.ts |
| Errors | r2ErrorInsight.ts |
| Settings | settings.ts, ui/SettingsTab.ts |
| UI | ui/R2ImageDeleteModal.ts, ui/coverImagePicker.ts |
| Commands | commands/index.ts |
MIT