File uploads
Send files through the endpoint, or upload large ones directly.
There are two ways to attach files to a Submission. A plain form sends them in the request, up to 4.5 MB in total. For larger files, upload each one directly to storage first and then name it in a JSON Submission.
Every file is stored privately in your Workspace's Region and is only ever shown through short-lived signed links.
Through the endpoint
Add enctype="multipart/form-data" and a file input:
Limits per Submission:
- 4.5 MB for the whole request, files included.
- 10 files.
- Each file within your Plan's per-file cap (25 MB on the beta Plan, which only matters for direct uploads, since the request limit is lower).
Past the request limit or the file count the answer is 413 payload_too_large.
A single file over the Plan's cap gets 413 file_too_large. A file input left
empty sends no file and isn't an error.
If you test against a server with no file storage configured, a request that
carries files answers 501 uploads_unavailable. A request without files still
works.
Direct uploads
Use these for files over the 4.5 MB request limit. The request for upload URLs takes the same Submit Key, Allowed Origins and rate-limit checks as a submit. It doesn't run Turnstile: the Submission that names the files does.
1. Ask for upload URLs
Send up to 10 files. Each needs a name, a type (its content type) and its
size in bytes.
The answer has one upload per file:
A file over the Plan's per-file cap gets 413 file_too_large here, and a
request that would fill the Workspace's storage gets 429 plan_limit_reached.
A body that isn't {"files": [...]} in that shape gets 400 invalid_body.
2. Upload each file
PUT the file to its url with the headers you were given. The URL only
accepts the content type it was issued for, and it expires after an hour.
3. Send the Submission
Send JSON and put {"$upload": "<uploadId>"} where the file goes. Use an array
for several files in one field. The reference has to be the field's whole
value: one nested inside another object isn't read.
An upload that has expired, belongs to another Form or never received its file
gets 400 invalid_upload. Uploads that never join a Submission are deleted
after they expire.
Storage
Files count towards your Plan's storage (1 GB on the beta Plan). When it's
full, the endpoint answers 429 plan_limit_reached. Files of Spam Submissions
aren't stored, and files are deleted with their Submission.