R2 Media Sync is an Obsidian plugin that automatically uploads local media assets referenced in Markdown notes to Cloudflare R2, rewrites the Markdown links to public R2 URLs, and optionally deletes the local files.
It is designed for workflows where other tools create local image files inside your vault, such as PDF-to-Markdown converters, document importers, AI assistants, or batch import tools.
- Community review compatibility: Removed newer settings APIs while keeping
minAppVersionatv1.12.7+. - Standalone uploader flow: R2 Media Sync can upload pasted images, dropped images, selected image files, and existing local image links without requiring EzImage.
- Selected file upload command: Added a command to choose image files from your device and insert R2 Markdown image links into the active note.
- Configurable selected-file insertion: Selected-file uploads can insert a Markdown image, Markdown link, or plain URL.
- Delayed rewrite verification: Added a verification pass for paste/drop workflows so Obsidian editor timing does not overwrite the rewritten R2 URL.
- Safer local cleanup controls: Cleanup mode and review-folder settings remain visible when local cleanup is enabled, including on older Obsidian versions used for BRAT testing.
R2 Media Sync is a standalone Cloudflare R2 media uploader for Obsidian.
It can handle the common image-entry moments directly:
- pasted images
- dropped images
- selected image files
- local image links that already exist in Markdown
It also handles the next problem: other tools may create image files directly inside your vault and insert local image links into Markdown. Common examples include:
- PDF-to-Markdown converters that extract page images into the note folder.
- Document importers that generate local assets.
- AI assistants or automation tools that write Markdown and image files directly.
- Batch migration workflows that leave notes full of local image references.
In those cases, paste/drag upload hooks never run. R2 Media Sync watches the resulting Markdown files, uploads the referenced local images to Cloudflare R2, rewrites the links in-place, and can remove the local image files after a successful upload.
In short:
- Use R2 Media Sync by itself for pasted, dropped, selected, generated, or imported local images.
- EzImage is optional. R2 Media Sync can import or reuse EzImage's R2 settings, but does not require EzImage to upload images.
- Companion plugins such as Geo Capture can optionally use R2 Media Sync's local metadata cache, but R2 Media Sync does not depend on them.
- Watches newly created or modified Markdown files.
- Uploads pasted images after Obsidian inserts them into the current note.
- Uploads dropped images after Obsidian inserts them into the current note.
- Uploads selected image files from the command palette and inserts R2 Markdown image links.
- Lets selected-file uploads insert a Markdown image, Markdown link, or plain URL.
- Detects local image links:
![[image.png]]
- Uploads referenced image files to Cloudflare R2.
- Rewrites the note to use a public R2 URL.
- Optionally deletes the local image file after successful upload.
- Can read Cloudflare R2 settings from the EzImage plugin or use its own manual R2 settings.
R2 Media Sync fully supports Obsidian mobile.
- Background Auto-monitoring: Automatically detects newly inserted local images on mobile, uploads them to R2, and rewrites links to public URLs.
- Local Image Handling: Supports
Handle local image after uploadon mobile. After upload and link rewrite, the original local file can be moved to a review folder or Obsidian trash. - iCloud Sync Staging: Recommended when using iCloud sync. Setting the handling mode to "Move to review folder" (e.g.
_r2_media_review) keeps files in a safety staging area. Files there are not deleted automatically; clear them from the dashboard after confirming other devices render the R2 URLs correctly. "Move to Obsidian trash" removes the image from the vault through Obsidian's trash handling. - UI Constraints: The plugin does not create the desktop status bar on mobile.
Scan on startupis always disabled at runtime on mobile to ensure fast app startup.
Plugins such as EzImage handle pasted or dragged images very well, but some Obsidian workflows create files directly in the vault and write local links into Markdown. For example, a PDF conversion plugin may create files like _page_3_Picture_2.jpeg and insert:
Those files can clutter the vault and consume sync storage. R2 Media Sync cleans up that class of generated assets automatically.
- Configurable local cleanup after upload:
- move to Obsidian trash
- move to a review folder before manual cleanup
- Configurable scan scope:
- whole vault
- selected folders only
- Configurable excluded folders, such as
.obsidian,.git,Templates, or attachment folders. - Toggle support for Markdown image links and wiki image embeds separately.
- Use existing EzImage R2 settings or enter R2 credentials directly.
- Reuse previous uploads by file hash to avoid uploading identical image content again.
- Retry failed uploads before recording them as failed.
- Keep a local failed upload log for troubleshooting.
- View recent failed upload details in a modal.
- Open a sync dashboard with recent status, scope count, upload history count, failure count, review folder size, and maintenance actions.
- Repair broken local image links from upload history when the attachment file is already missing.
- Preserve JPEG GPS coordinates in a local metadata cache before rewriting images to R2 URLs, so companion tools such as Geo Capture can still use photo location data later.
- Show the latest sync state in the Obsidian status bar.
- Choose the plugin interface language: Auto, English, or Traditional Chinese.
- Manual command to upload selected image files and insert R2 Markdown links.
- Manual command to scan the current note.
- Manual command to scan the configured scope.
- Manual command to import EzImage settings.
R2 Media Sync uses conservative defaults for first-time installs:
- It does not scan on startup by default.
- It does not delete local images by default.
- You can run a manual scan on the current note before enabling broader automation.
- If local cleanup is enabled, review folder mode is the safer option for multi-device sync.
- Local cleanup is skipped automatically when the note still contains local image links after rewrite.
- Failed uploads are recorded locally so you can inspect and retry later instead of losing track of partial failures.
- On mobile, startup scans are forced off at runtime for performance and safety.
After confirming your R2 settings, public URL, and scan scope, you can opt in to startup scans or local deletion from the plugin settings.
- No telemetry.
- No analytics.
- No remote service other than the Cloudflare R2 endpoint you configure.
- Credentials are stored locally in Obsidian plugin data when using manual mode.
- When using EzImage mode, this plugin reads the EzImage
data.jsonfile from your vault config folder locally and does not modify it. - Public Markdown links will contain your configured public R2 URL.
- Upload history and failed upload logs are stored locally in this plugin's data folder.
- When JPEG GPS metadata is available, only the extracted coordinates are stored in the local image metadata cache. Full EXIF data and image files are not copied into that cache.
Before uploading a JPEG image, R2 Media Sync reads local EXIF GPS coordinates when available and stores a small local record in:
.obsidian/plugins/cloudflare-media-sync/image_metadata.json
Each record is keyed by file hash and includes the original vault path, file name, R2 object key, public URL, related Markdown paths, upload time, and optional GPS coordinates.
This helps multi-plugin workflows. For example, after a photo is rewritten to an R2 URL or the local attachment is no longer available on another device, Geo Capture can still look up the saved GPS point and suggest nearby places.
This cache is local to your vault and is intended for recovery and interoperability. It does not upload location data anywhere by itself.
R2 Media Sync scans Markdown files so it can find local media references that were created by PDF converters, importers, AI tools, or other automation.
You can limit this behavior by:
- Choosing
Only included foldersinstead ofWhole vault. - Setting included folders such as
AI 工作區or a specific project folder. - Excluding folders such as
.obsidian,.git,.trash,Templates, or attachment folders.
The plugin only uploads image files that are referenced by Markdown notes and are inside the configured scan scope.
- Obsidian desktop or mobile.
- A Cloudflare R2 bucket.
- A public R2 URL or custom public domain.
- R2 API credentials with permission to upload objects to the target bucket.
This plugin is not yet published to the Obsidian community plugin registry.
If you use the BRAT plugin:
- Install and enable BRAT.
- Run
BRAT: Add a beta plugin for testing. - Enter:
fab34/cloudflare-media-sync
- Enable
R2 Media Syncin Community plugins.
Manual installation:
- Download or build the plugin files:
manifest.jsonmain.jsstyles.css
- Create this folder in your vault:
.obsidian/plugins/cloudflare-media-sync/
- Copy the three files into that folder.
- Restart Obsidian.
- Open
Settings -> Community plugins. - Enable
R2 Media Sync.
Open Settings -> R2 Media Sync.
Choose one:
Auto: follow your system/browser language when it is Traditional Chinese, otherwise English.EnglishTraditional Chinese
Choose one:
Read from EzImage: reuse EzImage's R2 settings.Manual: enter Cloudflare R2 settings directly in this plugin.
Manual fields:
- Cloudflare account ID
- Access key ID
- Secret access key
- Bucket name
- Public URL
- Path template
Reuse uploads by file hash: avoids uploading identical image content more than once by storing a local hash-to-URL history.Upload retry attempts: retries each R2 upload before the image is recorded in the failed upload log.
The Upload selected image files command can insert one of three formats:
- Markdown image:
 - Markdown link:
[image](https://example.com/image.png) - Plain URL:
https://example.com/image.png
Local cleanup is disabled by default.
When enabled, choose one cleanup mode:
Move to Obsidian trash: removes the image from the vault through Obsidian's trash handling.Move to review folder: moves uploaded local files into a vault folder such as_r2_media_review, preserving the original path under that folder so you can inspect before deleting.
The review folder is not cleared automatically. Use Clear local review folder from the command palette or dashboard when you are ready to move review-folder files to Obsidian trash.
Default:
{yyyy}/{MM}/{timestamp}-{random}.{ext}
Supported tokens:
{yyyy}{MM}{dd}{hh}{mm}{ss}{timestamp}{random}{name}{ext}
Use the whole vault, or limit processing to included folders.
Recommended exclusions:
.obsidian, .git, .trash, Templates
- Keep
Handle local image after uploaddisabled. - Keep
Scan on startupdisabled. - Open a note with one or two local test images.
- Run
R2 Media Sync: Upload local images in current note. - Confirm the note was rewritten to public R2 URLs.
- Enable local cleanup or startup scans only after the manual test behaves as expected.
- Keep
Handle local image after uploaddisabled. - Keep
Scan on startupdisabled. - Insert one test image into a note on mobile.
- Wait for the local link to be rewritten to an R2 URL.
- Confirm the same note renders correctly on desktop before relying on the workflow for important captures.
EzImage is not required for this workflow. If you also use EzImage, note that EzImage's own local-save behavior may affect whether a local file remains in the vault after upload.
Open the command palette and search for R2 Media Sync.
Upload local images in current noteUpload selected image filesScan configured scope nowImport settings from EzImageShow failed upload summaryClear failed upload logClear local review folderRepair missing local image linksOpen sync dashboard
Before:

![[diagram.png]]After:

Install dependencies:
npm installBuild:
npm run buildDevelopment watch:
npm run devFor a manual release, include:
manifest.jsonmain.jsstyles.css
This repository is structured for eventual submission to the Obsidian community plugin directory.
Before submitting:
- Publish a GitHub release whose tag exactly matches the version in
manifest.json, for example0.1.2. - Attach
manifest.json,main.js, andstyles.cssto that release. - Open a pull request to
obsidianmd/obsidian-releases.
See COMMUNITY_SUBMISSION.md for the suggested entry and checklist.
This plugin intentionally only processes image files that are referenced by Markdown notes. It does not upload unreferenced orphan images, because doing so could remove files that are still being staged or reviewed.
For multi-device setups, the safest pattern is:
- Upload and rewrite first.
- Confirm the rewritten note renders on the other device.
- Only then consider removing local files or emptying the review folder.
MIT — Feel free to use, modify, and distribute. Issues and PRs are welcome.