Neon Object Storage
The demo path for attachment storage — no card, and no account you do not already have.
Neon's S3-compatible storage. Pick this one for a demo: it asks for no card, and if you already use Neon for Database it adds no new account.
Not for a real community
Egress counts against the project's 5 GB/month network allowance, shared with the database. When it runs out, Neon suspends the compute until the next cycle — taking your app down with it. For production use Cloudflare R2 instead.
Install the Neon CLI:
npm install -g neonctlAuthenticate with the same API key from Database. If you do not have one, generate it in the Neon Console under Account settings → API keys.
export NEON_API_KEY="PASTE_YOUR_KEY_HERE"Create the bucket with public read enabled:
neonctl buckets create opencall-uploads --access-level public_readThe name becomes R2_BUCKET. public_read is required: the app builds image
URLs directly, with no signature, so anonymous reads have to work.
In the Neon Console, open your branch → Credentials → Create
credential, with the storage:read and storage:write scopes.
Both values are shown only once:
token_id→R2_ACCESS_KEY_IDs3_secret_access_key→R2_SECRET_ACCESS_KEY
Take the branch's storage endpoint, in the format
https://br-<branch-id>.storage.c-1.<region>.aws.neon.tech → R2_ENDPOINT.
Set R2_REGION to the project's region — us-east-2 or eu-central-1.
The public URL is the endpoint plus the bucket name:
https://br-<branch-id>.storage.c-1.<region>.aws.neon.tech/opencall-uploadsThat, with no trailing slash, becomes NEXT_PUBLIC_R2_PUBLIC_BASE_URL.
The bucket lives on the branch
Neon storage branches with the project: deleting or resetting the branch takes the images with it. Point the variables at the branch that is actually serving the app.
Variables from this step
| Variable | Value |
|---|---|
R2_BUCKET | Bucket name (opencall-uploads) |
R2_ACCESS_KEY_ID | token_id of the credential |
R2_SECRET_ACCESS_KEY | s3_secret_access_key |
R2_ENDPOINT | The branch's storage endpoint |
R2_REGION | us-east-2 or eu-central-1 |
NEXT_PUBLIC_R2_PUBLIC_BASE_URL | <endpoint>/opencall-uploads |
R2_ACCOUNT_ID stays empty on this path — it only applies to Cloudflare.