Buckets

A bucket is a logical container for objects. Each bucket carries a public/private flag, optional per-file size cap, optional total bucket size cap, an allow-list of MIME types, and an owner. Buckets are :storage.Bucket nodes - visible to Cypher, GraphQL, and RLS like any other graph entity.

Public vs Private

A public: true bucket serves objects via GET /storage/v1/object/public/{bucket}/{path} with no authentication. Flipping a bucket to private (or revoking its signing version) is the kill-switch for sharing.

# Toggle private at any time:
ALTER BUCKET avatars SET PUBLIC = false;

# Or via REST:
curl -X PUT -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"public": false}' \
  http://localhost:7474/storage/v1/bucket/avatars

Size limits

FieldScopeNotes
file_size_limitPer objectRejects upload with 413 if Content-Length exceeds
bucket_size_limitSum across bucketPre-flight + atomic check at write time
max_file_sizeGlobal (config)Server-wide ceiling (anvil.toml)
max_total_storageGlobal (config)Storage cap across every bucket
max_user_storagePer userTracked via :auth.User.storage_used

Allowed MIME types

If allowed_mime_types is non-empty, uploads with a Content-Type outside the list are rejected with 415.

CREATE BUCKET avatars
  PUBLIC = true,
  FILE_SIZE_LIMIT = 5242880,
  ALLOWED_MIME_TYPES = ["image/png", "image/jpeg", "image/webp"];

App Buckets

A bucket can belong to an app: pass "app": "<slug or id>" when creating it over REST (the DDL has no APP clause) and the slug is stored and returned as app on the bucket. It is fixed at creation. An app-scoped service-account key of that app - one whose privilege in the app is editor or admin - can then upload, download, list, delete and sign objects in the bucket, and nothing else under /storage: it cannot create, list or change buckets, its own included. A bucket it may not use answers 403 whether it is missing, has no app or belongs to another app. Give an app bucket a plain id; one that is also a route word (copy, move, list, sign, upload, public, signed) is never reachable by an app key.

curl -X POST -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"id": "crm-files", "public": false, "app": "crm"}' \
  http://localhost:7474/storage/v1/bucket
# => { "id": "crm-files", "app": "crm", ... }

# With an app key of crm (editor or admin in the app):
curl -X POST -H "Authorization: Bearer anvil_sk_..." \
  --data-binary @quote.pdf \
  http://localhost:7474/storage/v1/object/crm-files/quotes/q1.pdf
RouteAn app key may
POST / PUT / GET / HEAD / DELETE /storage/v1/object/{bucket}/{path}Upload, upsert, download (Range included), read metadata, delete
POST /storage/v1/object/list/{bucket}List
POST /storage/v1/object/sign/{bucket}/{path}Mint a signed download URL
POST /storage/v1/object/upload/sign/{bucket}/{path}Mint a signed upload URL

REST surface

MethodPathUse
POST/storage/v1/bucketCreate (optional app: the bucket belongs to that app)
GET/storage/v1/bucketList visible buckets
GET/storage/v1/bucket/{id}Fetch one
PUT/storage/v1/bucket/{id}Update settings
DELETE/storage/v1/bucket/{id}Drop (must be empty)
POST/storage/v1/bucket/{id}/emptyDelete every object inside
POST/storage/v1/bucket/{id}/sign-revokeInvalidate signed URLs