Skip to content

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

FieldTypeMeaning
idtext (PK)the bucket name — used in every object path
nametextdisplay name (defaults to id)
publicbooleanwhen true, objects are reachable via public URLs without auth
file_size_limitbigint · nullmax object size for this bucket, in bytes (or a string like "5MB" when creating)
allowed_mime_typestext[] · nullallowed content types (exact or type/*, e.g. image/*)

Bucket ids beginning with user- are reserved. A bucket named user-… (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 publicPOST/PUT with public: true returns 400 (“owner-scoped (user-) buckets cannot be public”). Pick a non-user- name for a public bucket. See Access Control.

The API

OperationMethod & path
CreatePOST /v1/storage/<id>/bucket { id, name?, public?, file_size_limit?, allowed_mime_types? }
ListGET /v1/storage/<id>/bucket
GetGET /v1/storage/<id>/bucket/<bucket>
UpdatePUT /v1/storage/<id>/bucket/<bucket> { public?, file_size_limit?, allowed_mime_types? }
EmptyPOST /v1/storage/<id>/bucket/<bucket>/empty
DeleteDELETE /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 limits
await 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 empty
await 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_limit is rejected with 413 Payload Too Large.
  • An object whose Content-Type isn’t in allowed_mime_types is rejected with 400 Bad Request. Wildcards like image/* 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