convex-chat
Guides

Attachment storage

Keep attachment bytes provider-neutral while preserving conversation authorization.

convex-chat stores provider-neutral attachment descriptors. Binary files, upload authorization, signed download URLs, and provider credentials belong to the host application.

The runnable example uses @convex-dev/r2 directly for image and recorded-audio messages. Upload grants are bound to a demo subject and conversation, expire after 15 minutes, and are verified again before the attachment message is committed. Audio is encoded in the browser with MediaRecorder; the example does not require a server-side media binary or transcoding step.

Cloudflare R2 setup

Create a private R2 bucket using the Standard storage class. For this checkout the suggested bucket name is convex-chat-dev.

Create an R2 API token restricted to this bucket with Object Read & Write permissions. Record its S3 Access Key ID, Secret Access Key, and endpoint. Set the values directly in the Convex development deployment—do not add them to an .env file or commit them:

cd apps/example
pnpm exec convex env set R2_BUCKET convex-chat-dev
pnpm exec convex env set R2_ENDPOINT
pnpm exec convex env set R2_ACCESS_KEY_ID
pnpm exec convex env set R2_SECRET_ACCESS_KEY

The CLI prompts for omitted values, which keeps secrets out of shell history. The R2 component does not use Cloudflare's displayed Token Value (R2_TOKEN).

Add this development CORS policy to the bucket, adjusting origins when the frontend port or production domain changes:

[
  {
    "AllowedOrigins": ["http://localhost:3000", "http://localhost:3001"],
    "AllowedMethods": ["GET", "PUT"],
    "AllowedHeaders": ["Content-Type"]
  }
]

Keep the bucket private. The sample generates short-lived, membership-checked download URLs. Publishing the bucket would bypass conversation access revocation.

Host storage responsibilities

The host creates authorized upload URLs, verifies authoritative object metadata, creates short-lived download URLs, and deletes objects. Message deletion returns the opaque attachment keys that the host should delete. This keeps R2, native Convex storage, S3, and other providers outside the durable chat domain without requiring a provider adapter package from convex-chat.

On this page