Files

Upload, manage, and serve files using Cloudflare R2 with presigned URLs and auto-thumbnails.

#File storage

EmuView stores files on Cloudflare R2 using a secure three-step presigned URL flow. Files upload directly from the browser to R2 — they never pass through the Worker, keeping your API fast and your costs low.

#What you can do

Action How
Upload a file Request an upload token, PUT to the presigned URL, confirm the upload
Download a file Request a presigned download URL (5-minute expiry)
Auto-thumbnails JPEG and PNG uploads get thumbnails generated automatically (per-container off switch)
Associate with records Store the file ID in a file field on any collection
Manage quota 5 GB per project by default (configurable)

#Key concepts

Concept What it means
Presigned URL A time-limited URL that grants direct access to R2 without going through EmuView
Upload token A response from POST /api/v1/files/upload-token containing the presigned upload URL and file ID
File field A collection field of type file that stores a reference to an R2 object
Size variant A resized version of an image (e.g., thumbnail) generated on upload confirmation

#Server-side variants

Variants are generated on the server when an upload is confirmed. Both halves of that are configurable, and each resolves container override → global setting → built-in default:

What Container setting Global setting Default
Whether it runs Server-side variants files.variants_enabled on
What it emits Variant format files.variant_format auto (webp, png/jpeg fallback)
Which sizes Thumbnail sizes files.image_sizes { "thumbnail": 320, "large": 1024 }

Turn Server-side variants off for a container whose derivatives are produced by another flow — otherwise the built-in pass writes variant objects beside every original that nothing will read. Leaving Thumbnail sizes blank does not do this: blank means inherit, and that chain ends at the built-in sizes.

Pinning a format writes that format or nothing, so no second format lands at a neighbouring key. auto keeps the historical behaviour: webp, falling back to png/jpeg only if the webp encoder cannot start.

Both apply to new uploads; use Regenerate on the container for existing files.

#HEIC photos are converted before they are uploaded

The server-side resizer has no HEIC decoder, so a HEIC stored as a HEIC would get no variants and nothing downstream could thumbnail it. The browser decodes HEIC wherever the platform does, so the upload component converts it to WebP on the device — before the PUT — and uploads that instead, with the filename and declared MIME rewritten to match (IMG_0421.HEIC becomes IMG_0421.webp).

The HEIC original is discarded, and with it any depth map, HDR gain map and 10-bit range the file carried. Only the WebP is stored; there is no copy of the original to recover. This is deliberate — the alternative is an original in the bucket that nothing can read.

A browser that can decode neither HEIC nor encode WebP refuses the upload with a message asking for a JPEG export, rather than storing an original that cannot be thumbnailed.

#Upload flow

1. Your app → POST /api/v1/files/upload-token (get presigned URL + file ID)
2. Your app → PUT {presignedUrl} (upload directly to R2)
3. Your app → POST /api/v1/files/confirm-upload/{id} (mark complete, trigger thumbnails)
4. Your app → Store fileId in a collection record's file field

Downloads follow the same presigned pattern — request a token, then fetch directly from R2:

sequenceDiagram
    participant App as Your app
    participant GW as Gateway Worker
    participant R2 as Cloudflare R2

    App->>GW: POST /api/v1/files/upload-token
    GW-->>App: Presigned PUT URL + file ID
    App->>R2: PUT file (direct upload, 120s window)
    App->>GW: POST /api/v1/files/confirm-upload/:id
    GW->>R2: Verify upload, generate thumbnails
    App->>GW: GET /api/v1/files/download-token/:id
    GW-->>App: Presigned GET URL (300s)
    App->>R2: GET file

The SDK wraps these three steps into a single sdk.files.upload() call — see SDK file methods. Endpoint parameters and error codes are in the files API reference.

#Storage limits

Limit Value
Default quota per project 5 GB
Max file size 100 MB per file (Cloudflare Workers limit)
Upload URL expiry 120 seconds
Download URL expiry 300 seconds

#Guides in this section

  • Upload flow — Step-by-step guide to uploading files from the browser
  • Download and serve — Get presigned download URLs and serve files
  • Thumbnails — Configure automatic image resizing and size variants