Core platformUpdated 2026-08-20
File storage and R2
Store private user files in R2 while keeping ownership and metadata in D1.
Storage model
R2 stores bytes. D1 stores file identity, owner, display name, content type, size, storage key, and timestamps.
Every storage key is user-namespaced. Every query includes the current user ID.
User workflow
Authenticated users can:
- upload a file;
- list their files;
- download through an ownership-checked route;
- delete both the R2 object and D1 metadata.
Downloads are streamed through /api/files/:id instead of exposing a public bucket URL.
Server operations
The file domain wraps the narrow storage contract with validation and persistence:
ts
await saveUserFile(db, session.user.id, file)
await getUserFile(db, session.user.id, fileId)
await deleteUserFile(db, session.user.id, fileId)Safety rules
- Enforce maximum size before writing the object.
- Normalize unsafe filenames for metadata and response headers.
- Never accept a client-provided storage key.
- Verify ownership on list, get, and delete.
- Remove user objects before deleting an account.
Add another storage provider
Implement the narrow put, get, delete, and list contract, then register the adapter. Product routes should remain unchanged.