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 Storage | Cloudflare R2 | |
|---|---|---|
| Card to enable | not required | required, even on the free tier (it just charges US$ 0 inside it) |
| Free storage | 5 GB per project | 10 GB per account |
| Egress | counts against the project's 5 GB/month network allowance | free, no egress charge |
| Extra account | none, if you already use Neon for Postgres | one more account |
| Maturity | beta | GA |
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
| Variable | Neon | Cloudflare R2 |
|---|---|---|
R2_BUCKET | Bucket name | Bucket name |
R2_ACCESS_KEY_ID | token_id of the credential | From the API Token |
R2_SECRET_ACCESS_KEY | s3_secret_access_key | From the API Token |
NEXT_PUBLIC_R2_PUBLIC_BASE_URL | <endpoint>/<bucket> | The bucket's public URL |
R2_ENDPOINT | The branch's storage endpoint | (empty) |
R2_REGION | us-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.