Bucket Management
A bucket is a named container for objects. In AnvilBase every bucket is a key
prefix inside the project’s single backing S3 bucket (<bucket>/<path>), so
buckets are cheap — creating one writes a metadata row, not a new S3 bucket.
Bucket metadata lives in a per-project storage.buckets table (schema storage,
matching Supabase) so it stays isolated per tenant and can back
access policies. It is created automatically when a project
is provisioned (and lazily ensured for projects created before this feature
shipped).
Bucket properties
| Field | Type | Meaning |
|---|---|---|
id | text (PK) | the bucket name — used in every object path |
name | text | display name (defaults to id) |
public | boolean | when true, objects are reachable via public URLs without auth |
file_size_limit | bigint · null | max object size for this bucket, in bytes (or a string like "5MB" when creating) |
allowed_mime_types | text[] · null | allowed content types (exact or type/*, e.g. image/*) |
Bucket ids beginning with
user-are reserved. A bucket nameduser-…(e.g.user-files,user-avatars) opts into per-user owner-prefix RLS: objects are confined to<bucket>/<user_id>/…and the bucket cannot be made public —POST/PUTwithpublic: truereturns400(“owner-scoped (user-) buckets cannot be public”). Pick a non-user-name for a public bucket. See Access Control.
The API
| Operation | Method & path |
|---|---|
| Create | POST /v1/storage/<id>/bucket { id, name?, public?, file_size_limit?, allowed_mime_types? } |
| List | GET /v1/storage/<id>/bucket |
| Get | GET /v1/storage/<id>/bucket/<bucket> |
| Update | PUT /v1/storage/<id>/bucket/<bucket> { public?, file_size_limit?, allowed_mime_types? } |
| Empty | POST /v1/storage/<id>/bucket/<bucket>/empty |
| Delete | DELETE /v1/storage/<id>/bucket/<bucket> |
Delete requires an empty bucket (matching Supabase). To remove a bucket that
still has objects, call empty first, then delete.
From the SDK
// Create a private bucket with limitsawait db.storage.createBucket('avatars', { public: false, fileSizeLimit: '5MB', allowedMimeTypes: ['image/png', 'image/jpeg'],})
await db.storage.listBuckets()await db.storage.getBucket('avatars')await db.storage.updateBucket('avatars', { public: true })
// Delete requires the bucket to be emptyawait db.storage.emptyBucket('avatars')await db.storage.deleteBucket('avatars')The same methods exist in every SDK (create_bucket / createBucket, etc.).
Per-bucket upload limits
When a bucket sets file_size_limit and/or allowed_mime_types, uploads are
checked against them in addition to the global per-request cap and the
project’s storage quota:
- An object larger than
file_size_limitis rejected with413 Payload Too Large. - An object whose
Content-Typeisn’t inallowed_mime_typesis rejected with400 Bad Request. Wildcards likeimage/*are supported.
Buckets created implicitly (by uploading to a bucket name that has no metadata row) impose no extra limits — only the global cap and project quota apply.
What’s next
- Public URLs — serve objects from
publicbuckets without auth. - Access Control — per-user owner-prefix RLS for private buckets.
- Working with Objects — upload, download, move, copy.