GeneralCloudflare R2Intermediate10 min read

How to Add File Uploads with Cloudflare R2

Let users upload files like images, PDFs, and documents to Cloudflare R2 from your vibe coded app. Works with Cursor, Bolt, and Lovable.

Before you start

  • A Cloudflare account
  • An existing web app with a backend (Next.js, Express, etc.)
  • Basic understanding of API routes

Step by step

1

Create an R2 bucket

In the Cloudflare dashboard, go to R2 → Create Bucket. Name it something like 'uploads'.

# Cloudflare Dashboard → R2 → Create Bucket
# Bucket name: uploads
# Location: Automatic
2

Create API credentials

Go to R2 → Manage R2 API Tokens → Create API Token. Choose 'Object Read & Write' permission for your bucket.

R2_ACCESS_KEY_ID=your-access-key
R2_SECRET_ACCESS_KEY=your-secret-key
R2_BUCKET_NAME=uploads
R2_ACCOUNT_ID=your-account-id
3

Install the AWS SDK

R2 is S3-compatible, so you use the standard AWS SDK to interact with it.

npm install @aws-sdk/client-s3 @aws-sdk/s3-request-presigner
4

Create the upload API route

Build a server-side route that generates a presigned URL for direct upload from the browser.

Paste this into Cursor:

Create an API route at /api/upload that:
1. Creates an S3Client configured for Cloudflare R2 using the R2 credentials
2. Generates a presigned PUT URL for the file using getSignedUrl
3. Uses a unique filename with timestamp + original name
4. Sets the presigned URL to expire in 60 seconds
5. Returns the presigned URL and the final public URL
5

Build the upload component

Create a frontend component that lets users pick a file and uploads it directly to R2 using the presigned URL.

Paste this into Cursor:

Create a FileUpload component that:
1. Has a drag-and-drop zone and a file picker button
2. Shows a preview for images
3. Calls /api/upload to get a presigned URL
4. Uploads the file directly to R2 using fetch with PUT method
5. Shows a progress indicator
6. Returns the public URL of the uploaded file
6

Enable public access for the bucket

If you want uploaded files to be publicly accessible, enable the R2 public bucket feature or set up a custom domain.

# Cloudflare Dashboard → R2 → your-bucket → Settings
# Public access → Enable
# Custom domain: uploads.yourdomain.com (optional)

Common errors

SignatureDoesNotMatch

The presigned URL credentials don't match.

Fix: Double-check your R2_ACCESS_KEY_ID and R2_SECRET_ACCESS_KEY. They must be R2-specific, not your global Cloudflare API key.

CORS error on upload

R2 blocks browser uploads by default.

Fix: Add a CORS policy to your R2 bucket: R2 → Settings → CORS → Allow your domain with PUT method.

File too large

The presigned URL has a content-length limit.

Fix: Set the maximum file size when generating the presigned URL. For large files, use multipart upload.

Related guides

Weekly Newsletter

Get next week's fix before you need it.

Join developers getting weekly vibe coding tips, error fixes, and tool updates.

Subscribe on Substack →