Platform
Hosting & deploysPush to a branch, get a live linkDatabaseReal Postgres, one per appSign-inEmail and Google, users includedFile storageUploads, photos and documentsPaymentsCheckout and plans, same projectAI gatewayEvery model, with a hard spend cap
Solutions
Moving an app overFrom Lovable, Bolt, Replit or v0Your first real appNever shipped anything beforeSide project → businessWhen people start paying youPricing$20/month plus prepaid credits
Resources
DocsGuides written for people who are newFor AI agentsClaude Code, Cursor, MCP, JSON CLIChangelogWhat shipped, most recent firstComponentsPaste-ready UI for sign-in and moreSupportA person answers, not a queue botRefer a friendBoth of you get credits Start for $20
Open in ChatGPT Open in Claude Last updated 2026-09-24

File storage

Uploads, photos and documents with signed, expiring upload links — in the same project as sign-in.

Profile pictures, receipts, PDFs — whatever your users send. Storage lives in the same project as sign-in, so a file can belong to a person without you wiring two services together.

How an upload works

Your app asks the project API for an upload URL, then the file goes straight to storage — it does not pass through your server:

POST /v1/projects/<slug>/storage/upload-url   →  { url, key, method: "PUT", expiresIn: 900 }
PUT  <the returned url>                       →  the file itself

The request body declares what you intend to receive — filename, contentType, size in bytes, and an optional prefix (default uploads/). The response's url is where the client PUTs the file, key is the storage key it lands at, and expiresIn is how many seconds the URL stays valid.

The URL is bound to what you declared, and the binding is enforced at the sink:

  • It expires after 900 seconds and works once — a successful PUT burns the token.
  • A declared size is enforced byte-for-byte; a different length is rejected.
  • A declared contentType is enforced against the request and, for images and PDFs (JPEG, PNG, GIF, WebP, PDF), checked against the file's real bytes — not just the extension the caller claimed.
  • Files larger than the folder's configured limit are refused before the URL is even issued.

So a leaked upload URL expires fast, and a renamed executable cannot pose as a photo.

The storage endpoints

Everything lives under /v1/projects/<slug>/storage, with your console session or a project API key:

MethodPathScopeWhat it does
GET/storage/statsstorage:readFile count, total bytes, and the project prefix
GET/storage/files?prefix=…storage:readList keys under a prefix — up to 200 per call, with a truncated flag
POST/storage/uploadstorage:writeMultipart upload (file field plus a path) through the API — for server-side files
POST/storage/upload-urlstorage:writeMint the signed PUT URL described above
DELETE/storage/files/<path>storage:writeDelete one object

Files are also listed, uploaded and deleted from the Storage page in the console, which uses these same routes.

Where files live

Every file lands under your project's prefix on the object store — R2_PREFIX is injected into your app's environment at deploy time, so server code can always tell where its files are. Keys returned by the API (uploads/photo.png, say) are relative to that prefix.

A bucket per environment

Previews write to their own bucket. A test upload on a branch cannot appear in your live app, and the preview's files are discarded with the branch.

Private by default

There is no public URL for an object. Upload URLs are signed and expire; the same posture applies on the way out — your app decides what to serve and to whom, and shares links that stop working rather than a bucket anyone can browse.

Limits

Storage is included in the $20 plan up to the launch cap, and heavier use draws prepaid credits — the same wallet as AI, so a spike stops at zero rather than billing your card.

Under the hood: object storage on Cloudflare R2 — each environment and preview branch gets its own key prefix under the project's bucket.

© 2026 Camplax $20 / month · credits stop at zero
Ask about Camplax