FormSubmit stores uploaded files in private object storage. They're linked from your notification email, your dashboard and integration payloads using signed links that expire after 7 days. Nothing is publicly listed.
Limits
| Plan | Per file | Total storage |
|---|---|---|
| Free | 10 MB | 100 MB |
| Starter | 10 MB | 250 MB |
| Pro | 10 MB | 1 GB |
| Business | 25 MB | 10 GB |
Storage is shared across all your forms. Deleting a submission — manually or through retention — deletes its files and frees the space immediately.
Option 1: plain HTML (up to ~4.5 MB per request)
Add enctype="multipart/form-data" and a file input:
<form action="https://formsubmit.app/f/YOUR_FORM_ID" method="POST" enctype="multipart/form-data">
<input type="email" name="email" required>
<input type="file" name="resume" accept=".pdf,.doc,.docx">
<input type="file" name="photos" accept="image/*" multiple>
<button type="submit">Send</button>
</form>Option 2: direct browser uploads (up to your plan's per-file limit)
For large files, upload each file straight to storage from the browser, then submit the form with references to the uploaded files.
- Request a short-lived upload token from
https://formsubmit.app/api/uploadsusing the Vercel Blob client, withclientPayloadset to{"formId":"YOUR_FORM_ID"}and a pathname starting withf/YOUR_FORM_ID/. - Upload the file.
- Submit the form with a
_filesfield containing a JSON array of{ field, url, filename }.
import { upload } from "@vercel/blob/client";
const FORM_ID = "YOUR_FORM_ID";
async function submit(form) {
const data = new FormData(form);
const files = [];
for (const input of form.querySelectorAll('input[type="file"]')) {
for (const file of input.files) {
const blob = await upload(`f/${FORM_ID}/${file.name}`, file, {
access: "private",
handleUploadUrl: "https://formsubmit.app/api/uploads",
clientPayload: JSON.stringify({ formId: FORM_ID }),
});
files.push({ field: input.name, url: blob.url, filename: file.name });
}
data.delete(input.name); // don't send the raw file again
}
data.set("_files", JSON.stringify(files));
return fetch(`https://formsubmit.app/f/${FORM_ID}`, {
method: "POST",
body: data,
headers: { Accept: "application/json" },
}).then((r) => r.json());
}The server verifies every referenced file belongs to your form, checks its real size against your plan, and attaches it to the submission. Files uploaded but never submitted are cleaned up automatically.
Where files show up
- Email: a list of attachment links (turn this off in notification settings).
- Dashboard: open a submission to preview images and PDFs or download any file.
- Integrations: webhooks receive a
filesarray withfield,filename,url,sizeandcontentType; Slack, Discord, Airtable and Google Sheets receive links.