Private Storage Uploads
Configure private S3-compatible uploads with policy, entitlement, quota, checksum, and durable deletion controls.
Synced with starter commit
7d452a6.
Set STORAGE_PROVIDER to s3, r2, or minio, then configure bucket, region, endpoint, and credentials. Objects stay private; browsers receive short-lived presigned URLs, never credentials. Configure bucket CORS for your web origin and required PUT headers, and do not enable ACLs unless the provider requires them.
Upload Contract
POST /api/storage/uploadsvalidates organization membership,storage.upload, the named policy (general,images,documents, orverified), file type, and the smallest of plan, environment, and policy size caps. It atomically reserves organization quota.- The browser PUTs bytes directly to object storage. The uploader uses XHR only to expose progress.
POST /api/storage/uploads/completeverifies object metadata and optional SHA-256, then marks the row active.GET /api/storage/files/[uuid]?download=1returns an owner-scoped signed download URL.DELETE /api/storage/files/[uuid]soft-deletes the row and schedules durablestorage_object_deletework.
Abandoned uploads release their reservations after one hour. Deletion retries through the job queue, so the cron runner must be operating. Object keys are generated by the service; never accept a client-selected bucket key.
Before launch, test the complete flow, wrong MIME and checksum, quota races, expired URLs, cross-organization access, deletion retry, and cleanup. See docs/storage-providers.md in the starter for provider-specific CORS and endpoint details.