Upload files to Cloudflare R2 directly from Finder
Right-click, upload, get shareable URL - it's that simple
Features • Installation • Usage • Configuration • Contributing
- What is Cloudflare R2?
- Features
- Prerequisites
- Part 1: Cloudflare Account Setup
- Part 2: Create R2 Bucket
- Part 3: Generate R2 API Credentials
- Part 4: Install the Uploader
- Usage
- Configuration
- Troubleshooting
- Uninstallation
- Contributing
- License
Cloudflare R2 is an object storage service similar to AWS S3, but with zero egress fees. It's perfect for:
- 🖼️ Image & Media Storage - Host images, videos, and files
- 📦 Backups - Store backups without worrying about download costs
- 🌐 Static Website Assets - Serve website files globally
- 📊 Data Lakes - Store large datasets economically
Key Benefits:
- ✅ No egress fees (free downloads, unlike AWS S3)
- ✅ S3-compatible API (easy migration from AWS)
- ✅ Global distribution via Cloudflare's network
- ✅ Cost-effective ($0.015/GB storage per month)
- 🚀 One-Click Upload - Right-click any file in Finder → Upload to R2
- 🔒 Secure - API credentials stored locally, never committed to git
- 📋 Auto-Copy URLs - Automatically copies public URLs to clipboard
- 🎯 Multiple Files - Upload multiple files at once
- 📊 Progress Feedback - macOS notifications for upload status
- 🛠️ Easy Setup - Automated installation scripts
- 🐍 Virtual Environment - Clean Python dependency management
- 🌐 S3 Compatible - Uses AWS SDK via Cloudflare R2's S3-compatible API
Before you begin, make sure you have:
- ✅ macOS 10.15 or later
- ✅ Python 3.7 or higher (comes with macOS)
- ✅ Internet connection
- ✅ Cloudflare account (free - we'll set this up below)
If you already have a Cloudflare account with R2 enabled, skip to Part 2.
-
Go to Cloudflare signup page:
-
Enter your details:
- Email address
- Password (strong password recommended)
- Click "Create Account"
-
Verify your email:
- Check your inbox for verification email from Cloudflare
- Click the verification link
-
Login to dashboard:
- Go to: https://dash.cloudflare.com/
- Login with your credentials
-
Navigate to R2:
- In the Cloudflare dashboard, look at the left sidebar
- Click on "R2" (looks like a storage icon)
-
Enable R2 (if not already enabled):
- You may see a page saying "Get Started with R2"
- Click "Purchase R2 Plan" or "Enable R2"
-
Choose plan:
- Free Tier includes:
- 10 GB storage per month (free)
- 1 million Class A operations (writes) per month
- 10 million Class B operations (reads) per month
- No egress fees (unlike AWS S3!)
- Click "Proceed" or "Enable"
- Free Tier includes:
-
Add payment method (required even for free tier):
- Cloudflare requires a payment method on file
- Enter credit/debit card details
- Click "Continue"
- Note: You won't be charged unless you exceed free tier limits
-
Confirmation:
- You should now see the R2 dashboard
- You'll see "Create bucket" option
- Find your Account ID:
- In the R2 dashboard, look at the right sidebar
- You'll see "Account ID" with a long string like:
abc123def456ghi789jkl012mno345pq - Click the copy icon to copy it
- Save it in a text file - you'll need this later
✅ Part 1 Complete! You now have a Cloudflare account with R2 enabled.
A "bucket" is like a folder where your files will be stored in R2.
-
Click "Create bucket":
- In the R2 dashboard, click the blue "Create bucket" button
-
Choose a bucket name:
- Enter a name (lowercase, no spaces)
- Good examples:
my-files,uploads,website-assets - Bad examples:
My Files,uploads!@#,UPLOADS - Click "Create bucket"
-
Bucket created!
- You'll see your new bucket in the list
- Note the bucket name - you'll need this for configuration
-
Public Access (optional):
- If you want files to be publicly accessible via URL:
- Click on your bucket name
- Go to "Settings" tab
- Under "Public access", click "Allow Access"
- You can also set up a custom domain here
-
CORS Settings (optional):
- If you'll access files from web browsers:
- Go to "Settings" → "CORS"
- Add appropriate CORS rules
✅ Part 2 Complete! You now have an R2 bucket to store files.
To upload files programmatically, you need API credentials (S3-compatible).
-
Open R2 API Tokens page:
- In the R2 dashboard (main page showing your buckets)
- Look for "Manage R2 API Tokens" button (usually top-right)
- Click it
-
You'll see the API Tokens page:
- Lists any existing tokens (probably empty if this is your first time)
- Has a "Create API Token" button
-
Click "Create API Token":
- Click the blue "Create API Token" button
-
Fill in the form:
a) Token Name:
┌─────────────────────────────────┐ │ Token Name │ │ ┌─────────────────────────────┐ │ │ │ R2 Uploader Tool │ │ ← Type a descriptive name │ └─────────────────────────────┘ │ └─────────────────────────────────┘- Enter:
R2 Uploader Tool(or any name you prefer)
b) Permissions:
┌─────────────────────────────────────┐ │ Permissions │ │ │ │ ● Admin Read & Write ← SELECT THIS │ │ ○ Admin Read only │ │ ○ Object Read & Write │ │ ○ Object Read only │ └─────────────────────────────────────┘- Select:
Admin Read & Write - This allows full read/write access to buckets
c) Apply to buckets:
┌─────────────────────────────────────┐ │ Apply to buckets │ │ │ │ ○ Apply to all buckets │ │ ● Apply to specific buckets only │ ← SELECT THIS │ │ │ Select buckets: │ │ ☑ my-files │ ← CHECK YOUR BUCKET └─────────────────────────────────────┘- Select:
Apply to specific buckets only(more secure) - Check the bucket(s) you created in Part 2
d) TTL (Time to Live):
┌─────────────────────────────────────┐ │ TTL │ │ │ │ ● Forever ← SELECT THIS│ │ ○ 1 hour / 1 day / 1 week / etc. │ └─────────────────────────────────────┘- Select:
Forever(for convenience) - Or: Set custom expiration (e.g., 90 days for better security)
- Enter:
-
Review and create:
- Double-check your selections:
- ✅ Name: descriptive
- ✅ Permissions: Admin Read & Write
- ✅ Buckets: your bucket(s) selected
- ✅ TTL: Forever (or custom)
- Click "Create API Token"
- Double-check your selections:
🚨 CRITICAL: The Secret Access Key is shown ONLY ONCE!
After clicking "Create API Token", you'll see:
┌─────────────────────────────────────────────┐
│ ✅ API Token Created Successfully │
│ │
│ Access Key ID: │
│ ┌─────────────────────────────────────────┐ │
│ │ 1234567890abcdef1234567890abcdef [📋] │ │
│ └─────────────────────────────────────────┘ │
│ │
│ Secret Access Key: │
│ ┌─────────────────────────────────────────┐ │
│ │ 2f27529d12e80d1efc237f2c800e303f... [📋] │ │
│ └─────────────────────────────────────────┘ │
│ │
│ Jurisdiction-specific endpoint: │
│ ┌─────────────────────────────────────────┐ │
│ │ https://ACCOUNT_ID.r2.cloudflarestor... │ │
│ └─────────────────────────────────────────┘ │
│ │
│ ⚠️ WARNING: Save the Secret Access Key now! │
│ You won't be able to see it again! │
│ │
│ [Download .csv] [Done] │
└─────────────────────────────────────────────┘
DO THIS NOW:
-
Copy Access Key ID:
- Click the 📋 copy button next to Access Key ID
- Paste it into a secure text file or password manager
- Example:
1234567890abcdef1234567890abcdef
-
Copy Secret Access Key:
- Click the 📋 copy button next to Secret Access Key
- Paste it into the same secure location
- Example:
a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6a7b8c9d0e1f2
-
Optional - Download CSV:
- Click "Download .csv" button
- Save the CSV file in a secure location (not in Downloads!)
- This is your backup
-
Click "Done" only after saving both values!
Before proceeding, make sure you have these 3 pieces of information:
✅ Account ID: abc123def456ghi789jkl012mno345pq
✅ Access Key ID: 1234567890abcdef1234567890abcdef
✅ Secret Access Key: 2f27529d12e80d1efc237f2c800e303f...
✅ Bucket Name: my-files
✅ Part 3 Complete! You now have R2 API credentials.
Option A - Using Git (recommended):
git clone https://github.com/sergebulaev/r2-uploader.git
cd r2-uploaderOption B - Download ZIP:
- Click the green "Code" button on GitHub
- Select "Download ZIP"
- Extract the ZIP file
- Open Terminal and navigate to the extracted folder:
cd ~/Downloads/cloudflare-r2-uploader
./scripts/setup.shWhat this does:
- ✅ Checks Python 3 installation
- ✅ Creates a virtual environment
- ✅ Installs
boto3(AWS SDK for Python) - ✅ Creates
config.inifrom template - ✅ Opens
config.inifor editing
The setup script will open config.ini in your default text editor.
Replace the placeholder values with your actual credentials:
[cloudflare]
# Your Cloudflare Account ID (from Step 1.3)
account_id = abc123def456ghi789jkl012mno345pq
# R2 API Token credentials (from Step 3.3)
access_key_id = 1234567890abcdef1234567890abcdef
secret_access_key = a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6a7b8c9d0e1f2
# Your R2 bucket name (from Step 2.1)
bucket_name = my-files
[upload]
# Optional: Custom path prefix in the bucket (e.g., "uploads/" or leave empty)
path_prefix =
# Optional: Make uploaded files public (requires bucket to have public access)
make_public = false
# Optional: Auto-copy public URL to clipboard after upload
copy_url_to_clipboard = trueSave and close the file:
- Press
Cmd + Sto save - Close the editor
./scripts/test_upload.shExpected output:
=========================================
Cloudflare R2 Uploader - Test
=========================================
Created test file: /tmp/r2-upload-test-1234567890.txt
Testing upload...
Uploading r2-upload-test-1765565640.txt (119.00 B)...
✓ Successfully uploaded: r2-upload-test-1765565640.txt
============================================================
Upload complete: 1 succeeded, 0 failed
Uploaded files:
- r2-upload-test-1765565640.txt
→ https://my-files.abc123def456ghi789jkl012mno345pq.r2.cloudflarestorage.com/r2-upload-test-1765565640.txt
📋 URL copied to clipboard!
=========================================
Test complete!
=========================================
✅ If you see this, your setup is working!
❌ If you see errors:
- Check your credentials in
config.ini - Verify bucket name is correct
- Make sure you copied the full Secret Access Key
- See Troubleshooting section below
Option A: Automatic Installation (Recommended)
./scripts/install_automator.shWhat this does:
- ✅ Creates a macOS Quick Action
- ✅ Installs it in
~/Library/Services/ - ✅ Makes it available in Finder's right-click menu
First-time permissions:
- macOS may ask for permissions to access folders
- Click "OK" or "Allow" when prompted
Option B: Manual Installation
If the automatic installer doesn't work, follow these steps:
-
Open Automator:
- Press
Cmd + Space - Type "Automator"
- Press Enter
- Press
-
Create New Quick Action:
- Click "File" → "New" (or press
Cmd + N) - Select "Quick Action"
- Click "Choose"
- Click "File" → "New" (or press
-
Configure Workflow Settings (at the top):
Workflow receives current: [files or folders] ← Select this in: [Finder] ← Select this -
Add Run Shell Script Action:
- In the left sidebar, search for "Run Shell Script"
- Drag "Run Shell Script" to the workflow area on the right
- Configure it:
- Shell:
/bin/bash - Pass input: Select "as arguments"
- Shell:
-
Paste the Upload Script:
- Delete the default text in the script box
- Copy and paste this script:
#!/bin/bash # IMPORTANT: Update this path to match your installation location PROJECT_DIR="/Users/yourname/r2-uploader" VENV_PYTHON="$PROJECT_DIR/venv/bin/python3" UPLOAD_SCRIPT="$PROJECT_DIR/scripts/upload_to_r2.py" # Check if upload script exists if [ ! -f "$UPLOAD_SCRIPT" ]; then osascript -e 'display notification "Upload script not found" with title "R2 Upload Error"' exit 1 fi # Get file count FILE_COUNT=$# # Show notification osascript -e "display notification \"Uploading $FILE_COUNT file(s)...\" with title \"R2 Upload\"" # Upload files (use venv Python if available) # Note: "$@" preserves filenames with spaces if [ -f "$VENV_PYTHON" ]; then if "$VENV_PYTHON" "$UPLOAD_SCRIPT" "$@" 2>&1 | tee /tmp/r2-upload.log; then osascript -e "display notification \"Successfully uploaded $FILE_COUNT file(s)\" with title \"R2 Upload Complete\"" else osascript -e 'display notification "Upload failed. Check /tmp/r2-upload.log" with title "R2 Upload Error"' exit 1 fi else # Fallback to system Python if "$UPLOAD_SCRIPT" "$@" 2>&1 | tee /tmp/r2-upload.log; then osascript -e "display notification \"Successfully uploaded $FILE_COUNT file(s)\" with title \"R2 Upload Complete\"" else osascript -e 'display notification "Upload failed. Check /tmp/r2-upload.log" with title "R2 Upload Error"' exit 1 fi fi
⚠️ IMPORTANT: Replace/Users/yourname/r2-uploaderwith the actual path to this project on your Mac! -
Save the Workflow:
- Press
Cmd + S - Save As:
Upload to Cloudflare R2 - Where:
~/Library/Services/(this should be the default) - Click "Save"
- Press
-
Enable the Quick Action:
- Open System Preferences → Extensions → Finder
- Make sure "Upload to Cloudflare R2" is checked ✅
-
Test It:
- Create a test file:
echo "Test" > ~/Desktop/test.txt - Right-click
test.txtin Finder - Look for "Quick Actions" → "Upload to Cloudflare R2"
- Click it and watch for the success notification!
- Create a test file:
✅ Part 4 Complete! Everything is installed and configured.
-
Select file(s) in Finder:
- Navigate to any file you want to upload
- You can select multiple files (Cmd + Click)
-
Right-click the file(s):
- A context menu will appear
-
Select "Quick Actions" → "Upload to Cloudflare R2":
┌──────────────────────────┐ │ Open │ │ Open With ►│ │ Get Info │ │ ─────────────────────── │ │ Quick Actions ►│ │ └─ Upload to Cloudflare R2 ← Click here │ Share ►│ └──────────────────────────┘ -
Wait for notification:
- You'll see: "Starting upload..."
- Then: "Successfully uploaded N file(s)"
- The public URL is automatically copied to your clipboard!
-
Paste the URL anywhere:
- Press
Cmd + Vto paste the file URL - Example:
https://my-files.ACCOUNT_ID.r2.cloudflarestorage.com/filename.jpg
- Press
# Navigate to project folder
cd ~/path/to/cloudflare-r2-uploader
# Upload single file
venv/bin/python3 scripts/upload_to_r2.py ~/Documents/report.pdf
# Upload multiple files
venv/bin/python3 scripts/upload_to_r2.py file1.jpg file2.png file3.pdf
# Upload with full path
venv/bin/python3 scripts/upload_to_r2.py /Users/yourname/Desktop/image.pngOutput:
Uploading report.pdf (2.34 MB)...
✓ Successfully uploaded: report.pdf
============================================================
Upload complete: 1 succeeded, 0 failed
Uploaded files:
- report.pdf
→ https://my-files.abc123def456ghi789jkl012mno345pq.r2.cloudflarestorage.com/report.pdf
📋 URL copied to clipboard!
[cloudflare]
# Required: Your Cloudflare Account ID
account_id = abc123def456ghi789jkl012mno345pq
# Required: R2 API credentials
access_key_id = YOUR_ACCESS_KEY_ID
secret_access_key = YOUR_SECRET_ACCESS_KEY
# Required: Bucket name
bucket_name = my-files
[upload]
# Optional: Add prefix to file paths (e.g., "uploads/", "images/2024/")
# Leave empty to upload to bucket root
path_prefix =
# Optional: Make files publicly accessible (requires bucket public access)
make_public = false
# Optional: Copy URL to clipboard after upload
copy_url_to_clipboard = trueExample 1: Organize uploads by date
[upload]
path_prefix = uploads/2024/Files will be uploaded to: my-files/uploads/2024/filename.jpg
Example 2: Public files with clipboard disabled
[upload]
path_prefix =
make_public = true
copy_url_to_clipboard = falseExample 3: Project-specific uploads
[upload]
path_prefix = projects/website/images/
make_public = true
copy_url_to_clipboard = trueSolution 1: Enable in System Preferences
- Open System Preferences → Extensions → Finder
- Make sure "Upload to Cloudflare R2" is checked
Solution 2: Reinstall the workflow
./scripts/install_automator.shCheck your config.ini:
cat config.iniCommon issues:
- ❌ Access Key ID is incorrect (should be 32 characters)
- ❌ Secret Access Key is incomplete (should be 64 characters)
- ❌ Copied the wrong value from Cloudflare dashboard
- ❌ Extra spaces or line breaks in the values
Solution:
- Delete the API token in Cloudflare dashboard
- Create a new one (follow Part 3)
- Update
config.iniwith new credentials - Test:
./scripts/test_upload.sh
Check bucket name:
# In config.ini, make sure bucket_name matches exactly
bucket_name = my-files # ✅ Correct
bucket_name = my files # ❌ Wrong (has space)
bucket_name = My-Files # ❌ Wrong (wrong case)Verify bucket exists:
- Go to Cloudflare dashboard → R2
- Check your bucket name (case-sensitive!)
- Update
config.inito match exactly
Grant permissions:
- System Preferences → Security & Privacy → Privacy
- Check these sections:
- Automation → Allow Terminal/Script Editor
- Files and Folders → Allow access as prompted
Install Python 3:
# Check if Python 3 is installed
python3 --version
# If not installed, install via Homebrew
brew install python3Reinstall dependencies:
# Remove old venv
rm -rf venv
# Run setup again
./scripts/setup.sh# View the last upload log
cat /tmp/r2-upload.log
# Monitor uploads in real-time
tail -f /tmp/r2-upload.log-
Run the test script:
./scripts/test_upload.sh
-
Check test results:
- Read the output carefully
- Error messages usually indicate what's wrong
-
Open an issue:
- Go to GitHub Issues
- Include the error message
- Include your macOS version
- Include
cat /tmp/r2-upload.logoutput (remove any sensitive info first!)
rm -rf ~/Library/Services/Upload\ to\ Cloudflare\ R2.workflowcd ..
rm -rf cloudflare-r2-uploader- Go to Cloudflare dashboard → R2 → "Manage R2 API Tokens"
- Find your token ("R2 Uploader Tool")
- Click "Delete" or "Revoke"
- Confirm deletion
cloudflare-r2-uploader/
├── .github/
│ └── workflows/test.yml # CI/CD pipeline
├── scripts/
│ ├── upload_to_r2.py # Main upload script
│ ├── setup.sh # Installation script
│ ├── test_upload.sh # Test script
│ └── install_automator.sh # Automator installer
├── venv/ # Python virtual environment
├── config.ini # Your credentials (gitignored)
├── config.template.ini # Template for config
├── .gitignore # Git ignore file
├── requirements.txt # Python dependencies
├── LICENSE # Apache 2.0 License
├── README.md # This file
├── ARCHITECTURE.md # Technical documentation
├── CLOUDFLARE_R2_API_SETUP.md # Detailed API setup guide
├── CONTRIBUTING.md # Contribution guidelines
├── MANUAL_AUTOMATOR_SETUP.md # Manual workflow setup
└── TEST_RESULTS.md # Test documentation
- Store
config.inisecurely (it's already gitignored) - Use API tokens with minimal required permissions
- Scope tokens to specific buckets only
- Set token expiration dates (90 days recommended)
- Rotate tokens regularly
- Delete unused tokens from Cloudflare dashboard
- Share
config.iniwith anyone - Commit
config.inito git (already prevented by.gitignore) - Use "Apply to all buckets" unless necessary
- Give tokens "Forever" TTL in production environments
- Store credentials in plaintext outside of
config.ini - Share screenshots containing your credentials
-
Immediately revoke the token:
- Cloudflare dashboard → R2 → Manage R2 API Tokens → Delete
-
Create a new token:
- Follow Part 3 again
-
Update config.ini:
- Paste new credentials
- Test:
./scripts/test_upload.sh
Contributions are welcome! Please see CONTRIBUTING.md for guidelines.
# Fork the repo on GitHub
# Clone your fork
git clone https://github.com/YOUR_USERNAME/r2-uploader.git
cd r2-uploader
# Create a feature branch
git checkout -b feature/your-feature-name
# Make changes and test
./scripts/setup.sh
./scripts/test_upload.sh
# Commit and push
git commit -m "feat: add your feature"
git push origin feature/your-feature-name
# Open a Pull Request on GitHubA: Yes! The software is open source (Apache 2.0). Cloudflare R2 has a free tier with 10 GB storage/month.
A: Yes, the Apache 2.0 license allows commercial use.
A: Not directly, but you could modify the endpoint in the code to point to S3.
A: Currently only files are supported. Folder support is planned for a future release.
A: Large files work but upload in a single request. Multipart upload for better performance is planned.
A: Not yet. This version is macOS-only (uses Automator). Windows/Linux support is planned.
A: Only if you enable public access on your bucket AND set make_public = true in config.
A: Use the Cloudflare dashboard (R2 → your bucket → select file → Delete) or use Wrangler CLI.
A: Yes! In Cloudflare R2 bucket settings → Settings → Custom Domains.
- ✅ Initial release
- ✅ macOS Finder integration
- ✅ Python upload script with boto3
- ✅ Virtual environment support
- ✅ Clipboard URL copying
- ✅ MD5 verification
- ✅ Complete documentation
This project is licensed under the Apache License 2.0 - see the LICENSE file for details.
- ✅ You can use this commercially
- ✅ You can modify the code
- ✅ You can distribute it
- ✅ You can use it privately
⚠️ You must include the license and copyright notice⚠️ Changes must be documented
- 📖 Documentation: Read the guides in this repo
- 🐛 Bug Reports: Open an issue
- 💡 Feature Requests: Open an issue
- 💬 Questions: Discussions
- @bcherny for creating Claude Code - the best AI Agent ever
- Cloudflare for the excellent R2 service
- boto3 for the AWS SDK
- macOS Automator for Quick Actions functionality
- Python community for the amazing ecosystem
Made with ❤️ for the Claude Code community
Perfect for sharing screenshots with remote running Claude Code and other coding agents
Ready to get started? Jump to Installation