Skip to content

tieubao/r2-assets

Repository files navigation

assets

General-purpose image/file hosting on Cloudflare R2. Stores files via authenticated upload, serves them at public URLs. Currently used for embedding diagrams in Capacities notes, but designed to work for any use case (Obsidian assets, blog images, etc).

Why this exists

We capture learning notes from AI coding sessions and push them to Capacities as a personal knowledge base. These notes often include diagrams, architecture sketches, and screenshots that make the content useful months later. Capacities renders images via URL, but it doesn't host files. We needed somewhere to put them.

The obvious options are S3, GCS, or just throwing images into Google Drive. Google Drive doesn't give you a direct file URL that renders inline. It wraps everything in a viewer, which breaks markdown image embeds. S3 and GCS work, but they charge egress fees every time someone loads an image. For assets that get viewed repeatedly (notes you revisit, shared links), egress adds up.

R2 S3 GCS Google Drive
Egress Free $0.09/GB $0.12/GB Free
Direct URL Yes Yes Yes No (viewer only)
Free storage 10 GB 5 GB 5 GB 15 GB (shared)
Inline embed Yes Yes Yes No
Auth for reads None needed Presigned URLs or public Presigned URLs or public Share link required

Cloudflare R2 is S3-compatible object storage with zero egress fees and a generous free tier. Upload a file, get a permanent public URL, embed it in a note. No viewer wrappers, no expiring links, no surprise bills.

Architecture

Any client (Claude, curl, script)
  |
  v
POST /upload (auth'd, base64 or binary)
  |
  v
Cloudflare R2 (assets bucket)
  |
  v
GET /i/2026/03/diagram-name.png (public, cached forever)

Setup

npm install
wrangler login

# Create R2 bucket
wrangler r2 bucket create assets

# Generate upload token
export UPLOAD_TOKEN=$(openssl rand -hex 32)
echo "Save this: $UPLOAD_TOKEN"

# Set the secret
wrangler secret put UPLOAD_TOKEN

# Deploy
wrangler deploy

Live at: https://<your-worker>.workers.dev

API

POST /upload

Upload an image. Returns a public URL.

JSON body (base64):

curl -X POST https://<your-worker>.workers.dev/upload \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"data": "iVBORw0KGgo...", "mime": "image/png", "filename": "tcp-handshake"}'

Binary body:

curl -X POST https://<your-worker>.workers.dev/upload \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: image/png" \
  -H "X-Filename: tcp-handshake" \
  --data-binary @diagram.png

Response:

{
  "url": "https://<your-worker>.workers.dev/i/2026/03/tcp-handshake.png",
  "key": "2026/03/tcp-handshake.png",
  "size": 45231,
  "mime": "image/png"
}

GET /i/:key

Serve a file. Public, no auth. Cached 1 year (immutable content).

GET /health

Returns {"status": "ok"}.

Supported formats

Category Formats
Images PNG, JPEG, SVG, WebP, GIF
Documents PDF
Audio MP3, OGG, WAV
Video MP4, WebM
Text JSON, TXT, Markdown

Max 5MB per file.

Cost

R2 free tier: 10GB storage, 10M reads/month. Worker free tier: 100k req/day. Total: $0.

Current consumers

  • knowledge-capture skill -- uploads diagrams from Claude sessions, embeds public URLs in Capacities notes
  • (future) Obsidian vault image hosting
  • (future) Blog/static site assets

About

Cloudflare Worker for image hosting on R2

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors