DEV Community

Cover image for How is R2 used for object storage?
tabban Ghani
tabban Ghani

Posted on

How is R2 used for object storage?

Modern applications often need to store profile pictures, PDFs, documents, videos, and other media. Instead of storing these files directly in a database, we can use object storage.

Let's understand the complete flow with a simple example: uploading a profile picture.

R2 and the Database

Suppose a user wants to upload:

profile-image.png

We separate the actual file from its application metadata:

DATABASE                         CLOUDFLARE R2
────────                         ─────────────
userId                           Actual image
fileKey  ──────────────────────> users/123/profile/abc.png
fileName
mimeType
fileSize
Enter fullscreen mode Exit fullscreen mode

The actual image is stored in R2, while our database stores information needed to reference and manage it.

How Does the Upload Work?

The basic architecture is:

Frontend
   │
   │ 1. Request upload
   ▼
Backend
   │
   │ 2. Validate file information
   │ 3. Generate unique fileKey
   │ 4. Generate Presigned PUT URL
   ▼
Frontend
   │
   │ 5. PUT actual file
   ▼
Cloudflare R2
   │
   ▼
Object Stored
Enter fullscreen mode Exit fullscreen mode

1. Frontend requests an upload

The frontend first sends information about the selected file:

POST /media/upload-url
Enter fullscreen mode Exit fullscreen mode
{
  "fileName": "profile-image.png",
  "mimeType": "image/png",
  "fileSize": 245760
}
Enter fullscreen mode Exit fullscreen mode

At this point, the actual image has not been uploaded.

2. Backend validates and generates a file key

The backend validates information such as the allowed file type and size.

It then generates a unique object key:

users/123/profile/550e8400.png
Enter fullscreen mode Exit fullscreen mode

This fileKey identifies where the object will be stored inside the R2 bucket.

3. Backend generates a Presigned PUT URL

Using server-side R2 credentials, the backend generates a temporary presigned PUT URL for that specific object key.

For example:

{
  "fileKey": "users/123/profile/550e8400.png",
  "uploadUrl": "<presigned-url>",
  "expiresIn": 600
}
Enter fullscreen mode Exit fullscreen mode

Conceptually, the URL provides temporary permission for:

Operation → PUT
Object    → users/123/profile/550e8400.png
Expiry    → 10 minutes
Enter fullscreen mode Exit fullscreen mode

The R2 credentials remain securely on the backend.

4. Frontend uploads directly to R2

The frontend now sends the actual image using the presigned URL:

PUT <presigned-url>
Content-Type: image/png

<actual image bytes>
Enter fullscreen mode Exit fullscreen mode

The important architecture is:

Frontend ───── actual file ─────> R2
Enter fullscreen mode Exit fullscreen mode

The large file doesn't need to travel through the application backend in this direct-upload design.

R2 validates the signed request and stores the object under the key used to create the URL.

5. Store the reference

After a successful upload, the application can save or finalize the metadata:

userId:    123
fileKey:   users/123/profile/550e8400.png
fileName:  profile-image.png
mimeType:  image/png
fileSize:  245760
Enter fullscreen mode Exit fullscreen mode

A more robust application can also call something like:

POST /media/complete
Enter fullscreen mode Exit fullscreen mode

after the R2 upload succeeds, allowing the backend to verify/finalize the uploaded media.

What Happens When the Presigned URL Expires?

Suppose the URL is valid for 10 minutes.

After 10 minutes:

Presigned URL       ❌ Expired
Uploaded R2 object  ✅ Still exists
Enter fullscreen mode Exit fullscreen mode

The permission is temporary, not the stored object.

Final Flow

The entire upload process can be remembered as:

Select File
    ↓
POST metadata → Backend
    ↓
Validate
    ↓
Generate File Key
    ↓
Generate Presigned PUT URL
    ↓
Return URL + File Key
    ↓
Frontend PUT → R2
    ↓
Actual File Stored
    ↓
Store / Finalize Metadata
Enter fullscreen mode Exit fullscreen mode

The core idea is simple:

The backend controls the upload, the presigned URL provides temporary permission, the frontend uploads directly to R2, R2 stores the actual file, and the database stores its reference.

Top comments (1)

Some comments may only be visible to logged-in visitors. Sign in to view all comments.