Skip to content

Cloudflare R2

Cloudflare R2 provides S3-compatible object storage for:

  • Unit images (floor plans, exterior, interior, site plans)
  • Agent avatars
  • Brochures and exposes
  • Energy certificates
  • Portal logos
  • Backups

Files are stored in private R2 buckets. The server never proxies file bytes — browsers interact with R2 directly via presigned URLs:

  1. Upload: Client requests a presigned PUT URL from the API → client uploads directly to R2 → client confirms upload to server
  2. Download: Client requests a presigned GET URL (with TTL) → client fetches directly from R2

The S3 client is configured in src/lib/r2/client.ts:

import { S3Client } from "@aws-sdk/client-s3";
const client = new S3Client({
region: "auto",
endpoint: `https://${R2_ACCOUNT_ID}.r2.cloudflarestorage.com`,
credentials: {
accessKeyId: R2_ACCESS_KEY_ID,
secretAccessKey: R2_SECRET_ACCESS_KEY,
},
requestHandler: new FetchHttpHandler(), // Works in Workers
});
FunctionModulePurpose
presignPut(key, contentType)src/lib/r2/presign.tsGet upload URL
presignGet(key, ttlSeconds)src/lib/r2/presign.tsGet download URL
headObject(key)src/lib/r2/presign.tsCheck object existence
BucketPurposeAccess
convertella-documentsUnit images, documents, brochuresPrivate (presigned URLs)
convertella-backupsDatabase and file backupsPrivate (operator only)

R2 is accessed via the S3 SDK over HTTPS, not R2 Workers bindings. This means switching to S3, Backblaze B2, or Tigris is just a configuration change:

  • Change endpoint URL
  • Update credentials
  • No code changes needed

On main, Directus manages file uploads (stored on disk or in R2 via Directus S3 adapter). The file_id column in unit_images references Directus file IDs. On spike/cf-runtime, the r2_key column is used instead (migration 0018).