Documentation

Storage (Cloudflare R2)

Cloudflare R2 file storage — bucket setup, credentials, presigned uploads, and S3-compatible API.

Open inChatGPT (opens in a new tab)Claude (opens in a new tab)Cursor (opens in a new tab)Cloudflare R2 file storage — bucket setup, credentials, presigned uploads, and S3-compatible API.

Motoko Base uses Cloudflare R2 for file storage. R2 is S3-compatible — the app uses the AWS SDK with a custom endpoint.

Official docs: Cloudflare R2 documentation

Setup

  1. Create an R2 bucket in Cloudflare dashboard → R2
  2. Create an R2 API token (Object Read & Write)
  3. Set env vars:
VariablePurpose
R2_ACCOUNT_IDCloudflare account ID
R2_ACCESS_KEY_IDAPI token access key
R2_SECRET_ACCESS_KEYAPI token secret
R2_BUCKET_NAMEBucket name (optional /prefix suffix)
R2_PREFIXOptional key prefix override
R2_ENDPOINTOptional custom endpoint

Without R2 env vars the app boots — the Storage demo shows a configuration message.

How uploads work

Client → Server Action (presigned PUT URL)
   ↓
Browser uploads directly to R2
   ↓
Server validates (HEAD) → marks file ready in Postgres

Files are never proxied through Next.js. The server issues a presigned URL; the browser uploads to R2. Metadata (name, size, owner) lives in the files table.

Upload limits and allowed MIME types: src/features/storage/config.ts.

S3-compatible API

R2 client: src/lib/r2.ts — uses @aws-sdk/client-s3 with:

  • Endpoint: https://{R2_ACCOUNT_ID}.r2.cloudflarestorage.com
  • Region: auto
  • Path-style URLs (forcePathStyle: true)

Presigned PUT (upload) and GET (download) URLs expire after 10–15 minutes.

Production

  • Use a private bucket — access only via presigned URLs
  • Set credentials as server-only env vars (never NEXT_PUBLIC_*)
  • Avatars (Settings) also use R2 when configured — same credentials
  • Consider lifecycle rules in Cloudflare for old objects — not shipped in the starter

For CORS: if browser uploads fail, add your app origin to the bucket CORS policy in Cloudflare. See R2 CORS docs.

Where to change this

WhatWhere
R2 client & presigned URLssrc/lib/r2.ts
Upload actions & validationsrc/features/storage/actions.ts
File metadata queriessrc/features/storage/queries.ts
Limits & MIME allowlistsrc/features/storage/config.ts
Storage UIsrc/features/storage/components/dashboard/storage-page.tsx

Import @/lib/r2 from server code only — never from client components.


Environment Variables — all R2 env vars.