STORAGE_CLIENT. Object keys carry the owning Store’s scope, so files from two Stores cannot collide, and the authenticated routes still do the access control.
Get this wrong on a stateless host and your Product images vanish on the next deploy. It is worth two minutes.
Which one to use
Local
Local storage writes to a directory on the same filesystem as the app. It is the default underdocker compose up and needs nothing external.
.env
GET /uploads/[...path] with immutable cache headers. SVG files get a restrictive Content Security Policy (CSP) of default-src 'none'; style-src 'unsafe-inline', because an SVG is a document that can carry script. PDFs are served as attachments rather than rendered inline.
Vercel Blob
On Vercel, attach a Blob store to your project. Vercel injectsBLOB_READ_WRITE_TOKEN for you.
.env
S3-compatible
Thes3 provider covers AWS S3, Cloudflare R2, MinIO, and Railway. Other S3-compatible endpoints may work. Upload a file, reload the page, and confirm the image renders before you trust one.
.env
MinIO (Docker Compose)
MinIO (Docker Compose)
The bundled Set
docker-compose.yml already runs MinIO:.env
STORAGE_PUBLIC_URL_MODE=proxy so upload URLs point at the Store at /uploads/.... The MinIO container hostname does not resolve in a browser.Cloudflare R2
Cloudflare R2
R2 speaks the S3 API. Your endpoint is in the R2 dashboard under bucket settings.
.env
Railway object storage
Railway object storage
Railway uses virtual-hosted-style URLs, so add
S3_VIRTUAL_HOSTED_STYLE=true to the standard S3 variables..env
Serving from your own domain
STORAGE_PUBLIC_URL_MODE decides what URL the upload API hands back. It applies to local and s3.
Reach for
proxy when MinIO is inside Docker Compose and the browser cannot reach the bucket, when you want every image served from your own domain, or when the bucket is private and access control belongs in the application.
.env
Limits and accepted types
Every upload goes throughPOST /api/upload, which is admin-only.
Files are validated against their magic bytes, so a fake
Content-Type header changes nothing. SVGs are additionally scanned for embedded scripts, event handlers, and javascript: URIs, and a file that fails returns 400.
Where files land
Uploads are stored atstores/{storeId}/{uuid} under the configured storage root. That storeId segment is what keeps one Store from reading or deleting another Store’s files.
Related pages
- Environment variables
- Deployment and hosting
- Secure a Store Runtime
- Store Admin for the upload endpoint
- Troubleshooting