File uploads
Accept resumes, screenshots, PDFs, or any other attachment alongside your form data. Files are stored privately and linked from the notification email.
The easy way: hosted forms
If you're using a hosted form, just add a File upload field in the builder. We handle the upload UI, progress, and storage end-to-end. Recipients get download links in their email; the files also live in your dashboard for as long as you keep the submission.
From your own HTML form
For forms hosted on your own site, you have two options:
Option 1: multipart/form-data
The simplest path — submit a normal HTML form with enctype="multipart/form-data". Files come through as attachments on the email.
<form
action="https://formto.email/f/YOUR_FORM_ID"
method="POST"
enctype="multipart/form-data"
>
<input name="name" required />
<input name="email" type="email" required />
<input name="resume" type="file" />
<button type="submit">Send</button>
</form>Option 2: direct-to-storage uploads (large files)
For files over a few MB, upload directly to our storage with a presigned URL, then submit the form with the upload IDs. This is the same flow our hosted forms use — fast, resumable, no proxy through the form endpoint.
- Request a presigned URL from
POST /api/uploads/submission/presignwith the form key, filename, mime type, and size. - PUT the file to the returned
uploadUrl. - Submit the form with a hidden
_uploadIdsfield set to the returned upload ID (comma-separated for multiple).
async function uploadAndSubmit(form) {
const file = form.querySelector('input[type=file]').files[0];
// 1. Presign
const presignRes = await fetch("/api/uploads/submission/presign", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
formKey: "YOUR_FORM_ID",
filename: file.name,
mimeType: file.type,
size: file.size,
fieldName: "resume",
}),
});
const presign = await presignRes.json();
// 2. Upload directly to storage
await fetch(presign.uploadUrl, {
method: "PUT",
headers: presign.headers,
body: file,
});
// 3. Submit form with the upload ID
const data = new FormData(form);
data.delete("resume"); // file is uploaded; don't double-send the bytes
data.set("_uploadIds", presign.uploadId);
return fetch("https://formto.email/f/YOUR_FORM_ID", {
method: "POST",
headers: { Accept: "application/json" },
body: data,
});
}Limits
- Up to 10 files per submission.
- Per-file size depends on your plan — see Limits.
- Total storage is also capped per plan. Old files can be deleted from the submissions dashboard.