A local-first, automated community plugin for Obsidian that provides direct, plaintext backup of notes and attachments to a Cloudflare R2 bucket.
- Plaintext Storage: Files are uploaded exactly as they are on your local computer, allowing you to browse, search, and recover single files directly from the Cloudflare R2 web interface.
-
Secure Credentials Storage: Cloudflare R2 credentials (AccountId, BucketName, AccessKeyId, SecretAccessKey) are stored in Obsidian's isolated
secretStorageAPI, which leverages OS-level keychains (like Windows Credential Manager) to keep them encrypted at rest. -
Hashed Local Database: Local sync records (
data.json) index files by their SHA-256 path hashes rather than plaintext path strings, keeping your folder structure and note titles private from local file scanners. - Archive Sync by Default: Avoid accidental data loss; local deletions do not automatically delete remote backups unless configured otherwise.
- Lightweight & Dependency-Free: Sign requests using a custom AWS Signature Version 4 implementation without bloated S3 SDKs.
-
Smart Idle Debounce: Automatically backs up files in the background after
$X$ minutes of keyboard/mouse inactivity to prevent sync conflicts. - Frictionless Recovery & Quick Controls: Fully accessible controls are built directly into a clickable status bar menu (in the bottom-right of your Obsidian workspace) for manual syncs, restoring backups, and settings access.
- Create a Cloudflare account and create an R2 bucket.
- Go to R2 -> Manage R2 API Tokens.
- Create a token with Edit (Read/Write) permissions. Note the Access Key ID, Secret Access Key, and Jurisdiction-specific Endpoint Account ID.
- Set up the CORS Policy for your bucket to allow Obsidian browser requests. Go to your bucket settings page in Cloudflare, select Settings -> CORS Policy -> Add CORS policy, and paste this JSON config:
[
{
"AllowedOrigins": ["app://obsidian.md"],
"AllowedMethods": ["GET", "PUT", "DELETE"],
"AllowedHeaders": [
"Content-Type",
"Authorization",
"X-Amz-Date",
"X-Amz-Content-Sha256",
"x-amz-date",
"x-amz-content-sha256"
],
"ExposeHeaders": ["ETag"],
"MaxAgeSeconds": 3600
}
]- Enable community plugins in Obsidian.
- Copy
main.jsandmanifest.jsoninto your vault's.obsidian/plugins/obsync/directory. - Reload Obsidian and enable the plugin in settings.
- Fill in your R2 credentials:
- Cloudflare Account ID (found in your Cloudflare dashboard URL)
- R2 Bucket Name
- Access Key ID
- Secret Access Key
- Click Test Connection to verify setup.
- Auto Sync: The plugin runs in the background and initiates backups after the vault has been idle for the configured time (default: 5 minutes).
- Status Bar Menu: Click the status indicator (e.g.
R2: Up to dateorR2: Idle) in the bottom-right corner to open a context menu with options to:Sync NowRestore Notes(downloads all notes from Cloudflare)Open Settings(jumps directly to ObSync settings)
- Safe Conflict Handling: During a restore, if a file already exists locally but has different content, it will be renamed to include
.conflictedso you never lose local edits.
src/
├── main.ts # Entry point: registers commands, ribbon, status bar, and menu
├── settings.ts # Settings UI (user-friendly credentials config and connection tester)
├── crypto.ts # Local helper functions (SHA-256 content hashing)
├── s3.ts # Lightweight AWS SigV4 PUT/GET/DELETE signer & R2 client
└── sync.ts # Sync engine (queuing, diffing, restore vault, conflicts)
The codebase includes comprehensive unit tests mocking the Obsidian environment and R2 client calls.
To run the test suite:
npm install
npm testTo build a production bundle:
npm run buildMIT