Skip to content

Search is only available in production builds. Try building and previewing the site to test it out locally.

Storage

Generally Available

Storage provides blob storage for files, images, documents, and large payloads. The HTTP surface is S3-shaped and familiar if you have used Supabase Storage or AWS S3.

Every organization gets named buckets. Objects support path-like keys (uploads/2026/report.pdf), multipart uploads for large files, and time-limited signed URLs for secure public access. Authorization combines Identity permissions with a shared policy DSL—the same engine Data uses, evaluated against object metadata instead of SQL rows.

Backends are swappable: local filesystem for development and self-hosted deployments, S3-compatible object storage (AWS S3, Cloudflare R2, MinIO) for production.

  • Bucket management — Create, list, update, and delete buckets per org.
  • Simple PUT/GET/HEAD/DELETE — Stream objects with correct content types and ETags.
  • Multipart uploads — S3-compatible protocol for files up to 5 TiB.
  • Signed URLs — HMAC signing on filesystem backend; native presign on S3.
  • Public buckets — Optional anonymous read for CDN-friendly assets.
  • Per-bucket policies — Fine-grained rules using object.* and auth.* builtins.
  • Range requests — Partial content downloads with Range: bytes=0-1023.

Create a bucket, upload an avatar, and generate a signed download URL.

Terminal window
reactor auth login user@example.com --password '...'
reactor storage buckets create avatars
reactor storage upload avatars user-123.png ./avatar.png --content-type image/png
reactor storage sign avatars user-123.png --ttl 3600
Terminal window
reactor storage list avatars --prefix uploads/ --limit 100

For files over 5 MiB, use multipart upload. Minimum part size is 5 MiB except the last part.

Terminal window
reactor storage multipart start backups archive.tar.gz
reactor storage multipart upload backups archive.tar.gz --part 1 --file part1.bin
reactor storage multipart upload backups archive.tar.gz --part 2 --file part2.bin
reactor storage multipart complete backups archive.tar.gz

Policies are defined per bucket and scope (read, write, delete).

policy tenant_isolation on bucket "avatars"
for read, write, delete
using (object.metadata->>'org_id' = auth.org_id()::text);
Terminal window
reactor storage policies apply avatars --file policies/avatars.sql
[storage]
backend = "fs" # "fs" or "s3"
signed_url_ttl_secs = 3600
max_object_size = 5368709120 # 5 GiB
# Filesystem backend (local dev, sizes 1–2)
[storage.fs]
root = "./.reactor/blobs"
signed_url_hmac_key = "base64-32-byte-key"
# S3 backend (production, sizes 5–6)
[storage.s3]
endpoint = "https://s3.amazonaws.com"
region = "us-east-1"
bucket = "my-reactor-storage"
access_key_id = "..."
secret_access_key = "..."
layout = "single_bucket" # prefix: {org_id}/{bucket}/{key}

Environment variables:

VariableDefaultDescription
REACTOR_STORAGE_BIND0.0.0.0:8003HTTP bind address
REACTOR_STORAGE_BACKENDfsfs or s3
REACTOR_STORAGE_FS_ROOTRequired for filesystem backend
REACTOR_STORAGE_S3_BUCKETRequired for S3 backend
REACTOR_STORAGE_MAX_OBJECT_SIZE5 GiBPer-object upload cap
LimitValueNotes
Max object size5 GiB (default)Configurable via max_object_size
Max object size (absolute)5 TiBWith multipart upload
Min multipart part size5 MiBExcept last part
Max parts per upload10,000S3-compatible limit
Max key length1,024 charactersNo .., null bytes, or control chars
Bucket name length3–63 charactersLowercase alphanumeric + hyphens
Signed URL default TTL1 hourConfigurable per request
Public bucket readsAnonymous allowedNo JWT required for GET

Permission scheme:

PermissionScope
storage:bucket:createCreate buckets
storage:{bucket}:readRead objects
storage:{bucket}:writeUpload objects
storage:{bucket}:deleteDelete objects
storage:{bucket}:adminUpdate/delete bucket
storage:*:*Full storage access
MethodPathDescription
POST/storage/v1/bucketsCreate bucket
PUT/storage/v1/buckets/{bucket}/objects/{*key}Upload object
GET/storage/v1/buckets/{bucket}/objects/{*key}Download object
HEAD/storage/v1/buckets/{bucket}/objects/{*key}Object metadata
DELETE/storage/v1/buckets/{bucket}/objects/{*key}Delete object
POST/storage/v1/buckets/{bucket}/objects/{*key}/signGenerate signed URL

Verify bucket name spelling and org context. Buckets are scoped to the active org—switch with X-Reactor-Org if needed.

The caller lacks storage:{bucket}:write or a bucket policy blocked the operation. Check object metadata against policy expressions.

Signed URLs include exp and sig query parameters. Regenerate if expired. During key rotation, both current and previous HMAC keys are accepted for a transition window.

Deleting a bucket with objects requires ?cascade=true or delete all objects first.

Abandoned multipart uploads accumulate until cleaned up (v0.2 sweeper). Abort explicitly:

Terminal window
curl -s -X DELETE "$REACTOR_URL/storage/v1/buckets/backups/objects/archive.tar.gz?uploadId=UPLOAD_ID" \
-H "Authorization: Bearer $REACTOR_TOKEN" \
-H "X-Reactor-Org: acme"