> ## Documentation Index
> Fetch the complete documentation index at: https://formpaste.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# File uploads

> Accept file attachments on a form - scanned, stored in R2, and downloaded from your dashboard - Pro.

<Info>**Pro** - a Free form rejects a multipart submission with attachments.</Info>

Formpaste can accept file attachments on your forms. Uploads are a standard
`multipart/form-data` POST to the same [`/submit`](/docs/quickstart) endpoint you already use —
no separate upload endpoint, no JavaScript required.

## Adding a file input

### Plain HTML (no JS)

Add `enctype="multipart/form-data"` to the form and a file input:

```html theme={null}
<form action="https://api.formpaste.com/submit" method="POST" enctype="multipart/form-data">
  <input type="hidden" name="access_key" value="YOUR_ACCESS_KEY">
  <input type="text" name="name" required>
  <input type="email" name="email" required>
  <textarea name="message"></textarea>
  <input type="file" name="attachment" multiple>
  <button type="submit">Send</button>
</form>
```

### React / Vue / Astro / any JS

Build a `FormData` and post it - don't set `Content-Type` yourself; the browser adds the
correct multipart boundary:

```js theme={null}
const form = new FormData();
form.set("access_key", "YOUR_ACCESS_KEY");
form.set("name", name);
form.set("email", email);
for (const file of fileInput.files) form.append("attachment", file);

const res = await fetch("https://api.formpaste.com/submit", {
  method: "POST",
  body: form,
});
```

## Limits

| Limit                             | Value      |
| --------------------------------- | ---------- |
| Per file                          | 3.5 MB     |
| Per submission                    | 3 files    |
| Per account (all forms, all time) | 5 GB total |

A file over the per-file limit, or a submission with too many files, is rejected outright —
nothing is silently dropped or truncated:

| Code                   | HTTP | Meaning                                             |
| ---------------------- | ---- | --------------------------------------------------- |
| `file_too_large`       | 413  | An uploaded file is too large.                      |
| `too_many_files`       | 400  | Too many files in this submission.                  |
| `empty_file`           | 400  | An uploaded file was empty.                         |
| `storage_cap_exceeded` | 413  | This account's file storage limit has been reached. |

## What happens to an uploaded file

1. The file uploads to storage (Cloudflare R2) and is held privately while it's
   **scanned**. Nothing is released before scanning completes - this is **fail-closed**:
   if scanning can't confirm a file is clean, it is not made available.
2. If the file passes, it becomes downloadable **from your dashboard** - open the
   submission and download the file. Downloads are owner-scoped: only the account that
   owns the form can fetch it, over a short-lived authenticated link. Files are never
   publicly reachable by URL.
3. If the file fails scanning (or is an unsafe/unrecognized type), it's blocked and never
   delivered to you.

Files are never emailed as attachments - your notification email links you to the
dashboard to view/download them.

**Retention:** a file is kept as long as its submission (Pro: 365 days), then deleted
automatically. Deleting a submission deletes its files immediately.

## Free vs. Pro

Free forms do not accept file uploads. Submitting a multipart request with attachments
against a Free-plan form is rejected with:

| Code               | HTTP | Meaning                           |
| ------------------ | ---- | --------------------------------- |
| `upgrade_required` | 403  | This feature requires a Pro plan. |

## Zero-config

No dashboard toggle to flip - any Pro-plan form accepts `multipart/form-data` submissions
with file fields as soon as you add a `<input type="file">` (or append files to a
`FormData`) in your form.
