OpenCall

How storage works

Optional — an S3-compatible bucket so people can attach images in chat. Without it, everything else still works.

This whole section is optional

You can finish the setup and run OpenCall without configuring any storage. Voice, video, screen sharing and text chat all work. The only thing you lose is attaching images in chat — the attachment button does not appear.

Skipping this is a reasonable choice for a demo. Come back whenever you want the feature.

What it is for

When someone attaches an image to a chat message, the file has to live somewhere that is not the app's own disk — the app restarts and redeploys, and anything written to its filesystem is lost.

So the image goes to an object storage bucket: a place that stores files and serves them back over HTTPS. The app speaks plain S3, which means any S3-compatible provider works. Two are documented here.

Which one to pick

Neon Object StorageCloudflare R2
Card to enablenot requiredrequired, even on the free tier (it just charges US$ 0 inside it)
Free storage5 GB per project10 GB per account
Egresscounts against the project's 5 GB/month network allowancefree, no egress charge
Extra accountnone, if you already use Neon for Postgresone more account
MaturitybetaGA

Demo → Neon: no card, and no account you do not already have (the demo already uses Neon for Database).

Production → Cloudflare R2: free egress is what makes it right for serving chat images to a real community.

On Neon, egress runs out with your database attached

The 5 GB/month of transfer is shared across the whole project, images and database alike — and when it runs out Neon suspends the compute until the next cycle. For a 5-person demo it is plenty; for a real community it is the wrong trade.

Common to both

There is no CORS policy to configure on either path: uploads go to /api/chat/uploads in Next.js, which writes to the bucket with the server credentials. The browser never talks to the bucket directly.

Variables from this step

VariableNeonCloudflare R2
R2_BUCKETBucket nameBucket name
R2_ACCESS_KEY_IDtoken_id of the credentialFrom the API Token
R2_SECRET_ACCESS_KEYs3_secret_access_keyFrom the API Token
NEXT_PUBLIC_R2_PUBLIC_BASE_URL<endpoint>/<bucket>The bucket's public URL
R2_ENDPOINTThe branch's storage endpoint(empty)
R2_REGIONus-east-2 or eu-central-1(empty — defaults to auto)
R2_ACCOUNT_ID(empty)Cloudflare Account ID

The R2_ prefix is historical — it names the storage slot, not the provider.

On this page