Skip to content

Storage Overview

AnvilBase Storage gives each project S3-compatible object storage, isolated in its own backing bucket (bucket-<project_id>) with its own server-side encryption key. Use it for user uploads, avatars, documents, exports — anything file-shaped.

The default backend is MinIO; an opt-in overlay swaps in RustFS (a Rust S3 server) — see Installation → Storage backend.

Concepts

  • Backing bucket — each project has one physical S3 bucket, isolated and encrypted (SSE-S3).
  • Logical buckets — within a project you organize objects into named buckets like avatars or documents. These are key prefixes inside the project’s backing bucket, so you can have as many as you like without provisioning. Bucket metadata (public flag, size/MIME limits) lives in a per-project storage.buckets table — see Bucket Management.
  • Object path — the key within a logical bucket, e.g. avatars/user-123/photo.jpg.
  • Reserved user- buckets — a bucket whose id starts with user- opts into owner-prefix RLS: objects are confined to <bucket>/<user_id>/… and it cannot be made public.

The API

Base path: /v1/storage/<project_id>/.... Core operations:

OperationMethod & path
UploadPUT /v1/storage/<id>/object/<bucket>/<path>
DownloadGET /v1/storage/<id>/object/<bucket>/<path>
DeleteDELETE /v1/storage/<id>/object/<bucket>/<path>
ListPOST /v1/storage/<id>/object/list/<bucket>
Signed URLPOST /v1/storage/<id>/object/sign/<bucket>/<path>
Move / CopyPOST /v1/storage/<id>/object/{move,copy}
Public URLGET /v1/storage/<id>/object/public/<bucket>/<path> (public buckets)
Image transformGET /v1/storage/<id>/render/image/{authenticated,public}/<bucket>/<path>
Bucket CRUDGET/POST /v1/storage/<id>/bucket, GET/PUT/DELETE …/bucket/<bucket>, POST …/bucket/<bucket>/empty
Resumable upload (large files)OPTIONS/POST /v1/storage/<id>/upload/resumable, HEAD/PATCH/DELETE …/upload/resumable/<id> (TUS 1.0.0)

With the SDK it’s the familiar db.storage.from('<bucket>') API — see Working with Objects.

Authentication & access control

Storage requests carry your project key (apikey) and, for a user, their JWT. You have three complementary options:

  • Signed URLs — mint a time-limited URL for exactly the object a user owns (the right default for private files). See Working with Objects → Signed URLs.
  • Public buckets — flip a bucket’s public flag and serve objects over an unauthenticated public URL (avatars, public assets).
  • Owner-prefix RLS — for a private bucket named user-…, objects under <bucket>/<user_id>/... are accessible only by that authenticated user; service_role bypasses. See Access Control.

Use the service_role key only in trusted server code.

Security: content-type handling

To prevent stored-XSS via uploaded files, AnvilBase classifies content types on download:

  • Safe inline types (png, jpeg, gif, webp, avif, pdf, json, text/plain) are served inline.
  • Dangerous types (html, svg, xml, …) are forced to Content-Disposition: attachment and served with X-Content-Type-Options: nosniff, so a browser downloads rather than executes them.

This happens automatically — you don’t configure it, but it’s why an uploaded .html file won’t render in place.

Encryption

Objects are encrypted at rest with SSE-S3 using a per-project key (configured by MINIO_KMS_SECRET_KEY). See Encryption. Combine with TLS in transit at Traefik.

Quotas

Project storage usage is enforced against max_storage_size_mb (project quotas). The quota is checked against the incoming object size (Content-Length, or the TUS Upload-Length for resumable uploads), so a project cannot overshoot its cap by one object. When a project is over quota, writes return 413 Payload Too Large with a JSON body:

{ "error": "storage_quota_exceeded", "bucket": "", "used_bytes": 0, "limit_bytes": 0 }

Limits

  • Direct uploads through the API are buffered, with a per-request size cap of 50 MB. For larger files, use resumable uploads (the TUS 1.0.0 protocol, backed by S3 multipart) — this lifts the 50 MB cap and uploads big objects in resumable chunks. The supabase-js resumable upload works drop-in. See Resumable Uploads.

What’s next