ShipKit.one
Storage

Cloudflare R2

Configure S3-compatible storage for uploads.

Overview

This project uses Cloudflare R2 via the S3-compatible API. Uploads are created through a presigned URL endpoint and stored under uploads/{userId}/....

Step 1: Create an R2 bucket

  1. Open the Cloudflare dashboard and select R2.
  2. Create a new bucket (example: shipkit-uploads).
  3. (Optional) Bind a public domain if you want public URLs.

Step 2: Create R2 access keys

  1. In R2 → API Tokens, create an R2 Access Key.
  2. Copy the Access Key ID and Secret Access Key.
  3. Copy your Account ID from the R2 overview page.

Step 3: Add environment variables

Use .env.local for local development:

R2_ACCOUNT_ID=your_account_id
R2_ACCESS_KEY_ID=your_access_key_id
R2_SECRET_ACCESS_KEY=your_secret_access_key
R2_BUCKET=shipkit-uploads
R2_PUBLIC_URL=https://pub-xxxx.r2.dev
# Optional: override endpoint (defaults to https://{accountId}.r2.cloudflarestorage.com)
R2_ENDPOINT=https://your_account_id.r2.cloudflarestorage.com

Notes:

  • R2_PUBLIC_URL is used to return a public URL after upload. If you do not expose the bucket publicly, leave it empty.
  • If you use a custom domain for the bucket, set it as R2_PUBLIC_URL.

Step 4: Configure CORS for uploads

Add a CORS rule on the bucket to allow browser uploads from your site:

[
  {
    "AllowedOrigins": ["http://localhost:3000"],
    "AllowedMethods": ["PUT"],
    "AllowedHeaders": ["*"],
    "ExposeHeaders": [],
    "MaxAgeSeconds": 3000
  }
]

Add your production domain when you deploy.

Step 5: Generate a presigned upload URL

The API endpoint requires a signed-in user:

curl -X POST http://localhost:3000/api/storage/presign \
  -H "Content-Type: application/json" \
  -H "Cookie: your_session_cookie" \
  -d '{
    "fileName": "logo.png",
    "contentType": "image/png"
  }'

Response:

{
  "success": true,
  "data": {
    "key": "uploads/{userId}/timestamp-logo.png",
    "uploadUrl": "https://...signed-url...",
    "publicUrl": "https://pub-xxxx.r2.dev/uploads/..."
  }
}

Step 6: Upload the file

Use the uploadUrl to PUT the file:

curl -X PUT "https://...signed-url..." \
  -H "Content-Type: image/png" \
  --data-binary "@/path/to/logo.png"

How it works in this repo

  • Presign endpoint: src/app/api/storage/presign/route.ts
  • R2 client: src/lib/storage/r2.ts

On this page