Request forms · 5 fields
Document upload form template
For when you have asked someone for a document: a signed contract, a proof of address, a certificate, a photo of a meter. The reference field ties the file to its case, so nothing is matched up by hand.
Document upload
Live demo: try the fields. Submitting sends nothing.
02 / Field guide
Why these fields
Name
text · requiredRequired, to match the document to a person.
Email
email · requiredRequired, to confirm receipt or ask for a better copy.
Reference number
text · requiredRequired, and the field that makes the upload useful. If your email link already knows the reference, change it to a hidden field and fill it in.
Document
file · requiredRequired. The accept attribute narrows the file picker to PDF and images, and Formward checks the type again on its side, refusing scripts, archives and executables.
Notes
textarea · optionalOptional, for anything the document does not explain.
When to use it
Use it instead of asking people to email attachments. Emailed files end up in several mailboxes and backups; files sent through the form stay in one place with a retention period.
File uploads need Professional. On Free and Personal, a submission carrying a file is refused, so use this template on a plan with uploads.
Privacy and GDPR
- Ask for exactly the document you need and say which pages. A full bank statement when you need one line is more than you can justify.
- Identity documents and anything showing health information deserve the shortest retention you can manage.
- Switch on link-only notifications so the email carries no submission content or file names, only a link to the dashboard.
03 / Make it yours
Choose your stack. Copy your form.
Replace the example endpoint with your Formward endpoint, then adapt the fields to your needs.
<form action="https://forms.formward.eu/f/your-form-id" method="POST" enctype="multipart/form-data">
<label for="name">Name</label>
<input type="text" name="name" id="name" required />
<label for="email">Email</label>
<input type="email" name="email" id="email" required />
<label for="reference">Reference number</label>
<small id="reference-help">The case, order or application number from our email.</small>
<input type="text" name="reference" id="reference" aria-describedby="reference-help" required />
<label for="document">Document</label>
<small id="document-help">PDF, JPG or PNG. Send only the pages we asked for.</small>
<input type="file" name="document" id="document" accept=".pdf,.jpg,.jpeg,.png" aria-describedby="document-help" required />
<label for="notes">Notes</label>
<textarea name="notes" id="notes"></textarea>
<input type="text" name="_gotcha" tabindex="-1" autocomplete="off" style="position:absolute;left:-9999px" aria-hidden="true" />
<button type="submit">Send</button>
</form>Accessibility
Accessible by default, and how to keep it that way
- Every field in the code has a visible label tied to it with for and id, so screen readers announce what to enter and a click on the label focuses the field.
- Required fields carry the required attribute, so the browser explains a missing answer before anything is sent. Say “required” in words as well; an asterisk or a colour alone is easy to miss.
- The spam trap field (_gotcha) is hidden from screen readers with aria-hidden and skipped by the Tab key, so people never meet it.
- In the JavaScript versions, show the confirmation or error in an element with role="status" so the result is announced, and keep the entered answers when sending fails.
Changed the fields? Paste your version into the HTML form tester to see exactly what it sends, or run it through the GDPR form checker.
04 / Implementation
How the code talks to Formward
Each form you create gets its own endpoint in the form https://forms.formward.eu/f/your-form-id. The HTML version posts to it directly and the browser is redirected to a thank-you page, so it works without JavaScript.
The Next.js version below sends the same fields with fetch and an Accept: application/json header. Formward then answers with { ok: true, id }, or an error body with 402 when the plan limit is reached and 429 when the rate limit kicks in. See responses and status codes.
Next.js client component
"use client";
import { useState } from "react";
export function DocumentUploadForm() {
const [sent, setSent] = useState(false);
async function onSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault();
const form = e.currentTarget;
const res = await fetch("https://forms.formward.eu/f/your-form-id", {
method: "POST",
headers: { Accept: "application/json" },
body: new FormData(form),
});
if (res.ok) {
setSent(true);
form.reset();
}
}
if (sent) return <p>Thanks! Your message has been sent.</p>;
return (
<form onSubmit={onSubmit}>
<label htmlFor="name">Name</label>
<input type="text" name="name" id="name" required />
<label htmlFor="email">Email</label>
<input type="email" name="email" id="email" required />
<label htmlFor="reference">Reference number</label>
<small id="reference-help">The case, order or application number from our email.</small>
<input type="text" name="reference" id="reference" aria-describedby="reference-help" required />
<label htmlFor="document">Document</label>
<small id="document-help">PDF, JPG or PNG. Send only the pages we asked for.</small>
<input type="file" name="document" id="document" accept=".pdf,.jpg,.jpeg,.png" aria-describedby="document-help" required />
<label htmlFor="notes">Notes</label>
<textarea name="notes" id="notes" />
<input
type="text"
name="_gotcha"
tabIndex={-1}
autoComplete="off"
style={{ position: "absolute", left: "-9999px" }}
aria-hidden="true"
/>
<button type="submit">Send</button>
</form>
);
}
Spam protection
- The generated code already includes the hidden _gotcha honeypot. Leave it in: a bot that fills it gets a normal success response, the submission is filed as spam and no email is sent.
- Every form has a per-IP rate limit, and you can restrict it to your own domains with the allowed-origins list in the form settings.
- Cloudflare Turnstile is optional and works on every plan, including Free. Turn it on in the form settings, add the widget to your markup and Formward checks the token server-side.
- The form settings can also reject disposable email addresses, which is worth turning on when a fake address would waste your time.
What happens after submit
- Each file is stored with its submission in Sweden. Signed-in members of your workspace download it from the dashboard; the notification email links to it instead of attaching it.
- Files are deleted with the submission, by hand or when the form's retention period ends. Count and size limits per plan are listed in the file uploads docs.
- From Professional, a webhook can tell your case system that a document has arrived.
Questions
- Which file types can people upload?
- Images (JPEG, PNG, GIF and WebP), PDF, plain text and CSV, and Office and OpenDocument files. Anything else is refused. This template narrows it further to PDF, JPG and PNG.
- Can I accept uploads on the free plan?
- No. Uploads start on Professional. On lower plans, ask for a link to the file instead, or have people reply to your email with it.
Related
- Forms with file uploads
Setup, limits and where files live.
- File uploads and the GDPR
Minimisation, retention and access control.
- File uploads reference
Types, size caps and error codes.
- Request templates
Other structured requests.
Customize before publishing
- Remove fields you do not need and mark only essential answers as required.
- Replace placeholder wording with terms your audience already understands.
- Add your privacy notice, retention details and expected response time.
- Test keyboard focus, validation errors and the confirmation state on mobile.
Use this template
Create a free account and the form is set up from this template. Copy your endpoint and replace https://forms.formward.eu/f/your-form-id in the code above.