> For the complete documentation index, see [llms.txt](https://kb.craftformswp.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://kb.craftformswp.com/documentation/form-building/create-form/file-uploads.md).

# File Uploads

The **File Upload Field** block lets visitors attach files to a form — a CV, a signed contract, artwork for a print job, an ID scan. This article explains where those files go, why they are not in your Media Library, and where to click to get one.

File uploads are a PRO feature.

#### The short version <a href="#the-short-version" id="the-short-version"></a>

| Question                          | Answer                                                                                                                                     |
| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| Where are my files?               | `wp-content/uploads/craftforms-files/`, in a folder your web server refuses to serve                                                       |
| Why not the Media Library?        | Anything in the Media Library has a public URL. Visitor uploads are private. [Full explanation](#why-uploads-are-not-in-the-media-library) |
| How do I open one?                | Click the filename in the submission, the order, or the WooCommerce order screen. [Where to look](#where-to-find-an-uploaded-file)         |
| How does the customer get theirs? | A private download link in their order confirmation and order emails                                                                       |
| Are they backed up?               | Yes — they are inside `wp-content/uploads`, like any other site upload                                                                     |

#### How an upload travels <a href="#how-an-upload-travels" id="how-an-upload-travels"></a>

1. **The visitor picks a file.** Nothing is sent yet — the browser only shows a preview. Nothing is stored on your server until the form is actually submitted, so an abandoned form leaves nothing behind.
2. **The form is submitted.** The file travels with it.
3. **CraftForms checks the file.** Size, file type, and filename — see [What gets checked](#what-gets-checked). A file that fails is rejected with an error and never stored.
4. **The file moves into private storage** and is given an unguessable ID.
5. **Everything that shows the submission links to it** — the submission record, the order, the WooCommerce order, notification emails.

Steps 3–5 happen in one request. There is no queue and nothing to configure.

#### The three CraftForms folders in wp-content/uploads <a href="#the-three-craftforms-folders-in-wp-contentuploads" id="the-three-craftforms-folders-in-wp-contentuploads"></a>

If you look inside `wp-content/uploads` you will see three CraftForms folders. They do different jobs, and only one of them holds anything long-term.

| Folder               | What's in it                                                                                                   | How long it stays                                   |
| -------------------- | -------------------------------------------------------------------------------------------------------------- | --------------------------------------------------- |
| `craftforms-files`   | **Visitor uploads.** The real, permanent store.                                                                | As long as the submission or order that shows it    |
| `craftforms-pdf`     | **PDFs CraftForms generated** — invoices, tickets, packing slips. Also a font cache used while rendering them. | As long as the record it's attached to              |
| `craftforms_uploads` | **Working space.** A file lands here for the few seconds between arriving and being checked.                   | Cleared hourly; nothing older than an hour survives |

So: two stores for two different kinds of file (files people gave you vs. files CraftForms produced), plus a scratch folder. If `craftforms_uploads` ever has anything in it, it is a file from the last hour or the leftovers of a submission that failed validation — it is safe to be empty, and safe to delete.

All three refuse direct web access. Each contains an `.htaccess`, a `web.config` and an index file so that Apache, nginx and IIS all decline to serve their contents, whatever your host runs.

Inside `craftforms-files`, files are stored in year/month folders under random names with a `.bin` extension, each paired with a small `.json` file holding the real filename and who owns it. That's deliberate: even if a server misconfiguration exposed the folder, there is nothing there a browser would agree to run or display, and no filename that hints at the contents.

#### Why uploads are not in the Media Library <a href="#why-uploads-are-not-in-the-media-library" id="why-uploads-are-not-in-the-media-library"></a>

**Short answer: everything in the Media Library has a public, permanent URL that anyone can open — no login, no link expiry. Form uploads are private, so they can't live there.**

Earlier versions of CraftForms did copy uploads into the Media Library. A security review in August 2026 changed that, for four reasons:

1. **Public URLs.** A Media Library file is served straight off your uploads folder. Anyone with the URL can open it forever — search engines included, if the link ever appears somewhere crawlable. That's correct for a product photo and wrong for a passport scan.
2. **Guessable neighbours.** Uploads sit in predictable `/2026/09/` folders with the names people gave them. One leaked URL suggests where to look for the rest.
3. **Too many people can see them.** The Media Library is visible to every user who can upload — Authors and Contributors on many sites. A CV sent to your hiring form should not be browsable by a guest blogger.
4. **Anything you can view, a browser can run.** Some file types the browser treats as active content. Served from your own domain, a hostile upload becomes a script running on your site.

What replaced it addresses all four. Files sit outside anything the web server will serve, are named unguessably, are reachable only through a link that checks who you are, and are always sent as a download rather than displayed in the page.

**Nothing was lost in the move.** You still see every upload in the same places you did before, still click a filename to open it, and files still get attached to notification emails. The only visible change is that the file no longer shows up in **Media → Library**.

**Uploads from before the change** are still in the Media Library, and their links still work. They were left where they were rather than moved, so that nothing already linked from an old order or email broke. Only uploads received after the change use the new store.

#### Where to find an uploaded file <a href="#where-to-find-an-uploaded-file" id="where-to-find-an-uploaded-file"></a>

**As a site administrator**

| Screen                                     | What you'll see                                                                                                                           |
| ------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------- |
| **CraftForms → Submissions** → click a row | The upload's filename in the field list, as a link. Click it to download.                                                                 |
| **CraftForms → Orders** → click a row      | Same — the filename is a link in the order's field list.                                                                                  |
| **WooCommerce → Orders** → edit an order   | An **Uploaded files** box in the right-hand sidebar listing every file on the order, plus the filename as a link on the line item itself. |

Your links work because you are logged in and have CraftForms access — they carry no secret, so pasting one into a chat gives nobody else access.

**As the customer**

| Where                                              | What they'll see                                  |
| -------------------------------------------------- | ------------------------------------------------- |
| **Order confirmation page** (right after checkout) | The filename and a download link on the line item |
| **My Account → Orders → View**                     | The same link, any time later                     |
| **WooCommerce order emails** (HTML)                | The filename, as a clickable link                 |
| **WooCommerce order emails** (plain text)          | The filename only — see the note below            |
| **CraftForms notification emails**                 | The file itself, **attached to the email**        |

A customer's link carries a private key unique to that one file. It doesn't expire, and it works without logging in — which is what makes it usable from an email. Treat it like an unlisted link: anyone the customer forwards it to can open that one file, and nothing else.

**One exception — plain-text order emails.** WooCommerce offers order emails in HTML (the default) or plain text. Plain text can't carry a link, so those recipients see the filename with no way to click through; they can still get the file from the order confirmation page or **My Account → Orders**. If your store sends plain-text emails and customers need the file straight from the inbox, use a CraftForms notification email instead — those attach the file itself.

Note the difference between the two email types. A **WooCommerce** order email links to the file. A **CraftForms notification email** attaches it, so whoever receives the notification has the document in hand without clicking anything. If you would rather link than attach in a CraftForms email, a single-file field's URL is available to email and PDF templates as `{{email.fieldname.url}}`.

**Not in**

* **Media → Library** — by design, see above.
* **The email's "All Form Data" block** — this lists the filename, not a link. The file is attached to the email instead.

#### What gets checked <a href="#what-gets-checked" id="what-gets-checked"></a>

Every upload has to pass all of these before it is stored:

* **Size** — the field's *Max file size* setting, 5 MB by default.
* **File type** — the field's *Allowed types* list (JPEG, PNG and PDF by default). CraftForms reads the file's actual contents to decide what it is, rather than trusting the browser or the file extension. A `.pdf` that isn't really a PDF is rejected.
* **Filename** — cleaned up, and the extension corrected to match what the file actually is.
* **Nothing executable or renderable.** Scripts, server pages and a handful of types a browser would run are refused outright.

An empty *Allowed types* list accepts nothing, rather than everything — a field you forgot to configure fails closed.

**Why the stored filename looks different**

Upload `FPR 29 26.pdf` and it is recorded as `FPR_29_26.pdf`. That's normal:

* Spaces, brackets and accents become underscores — `My Résumé (final).pdf` becomes `My_Resume__final_.pdf`.
* An underscore is added after any mid-name extension WordPress doesn't recognise (`.xml`, `.p7m` above), so the name can't be mistaken for a different file type.
* The extension is lower-cased and corrected to match what the file actually is.

The file's contents are untouched, and the adjusted name is what the customer sees when they download it.

#### Housekeeping <a href="#housekeeping" id="housekeeping"></a>

You don't have to do any of this — it runs on its own.

* **Working files** in `craftforms_uploads` are deleted hourly.
* **Unclaimed uploads** are collected after two days. An upload is unclaimed when no submission or order ended up showing it — for example a form with no *Save Submission* action, or a cart the visitor never checked out. Developers can change the two-day window with the `craftforms_unowned_file_grace_days` filter.
* **Deleting a submission or an order** removes its uploads — but only once nothing else still shows them. A WooCommerce upload appears on both the order and its CraftForms submission record, so deleting one leaves the other's link working. The file goes when the last of them does.

#### Turning uploads off <a href="#turning-uploads-off" id="turning-uploads-off"></a>

To disable file uploads site-wide regardless of licence — a public demo, a staging site — add this to `wp-config.php`:

```php
define( 'CRAFTFORMS_DISABLE_FILE_UPLOADS', true );
```

File Upload fields then show nothing on your site, and in the form editor they say "This block isn't available on this site." See [wp-config.php Constants](/documentation/for-developers/wp-config-constants.md) for this and the other settings you can set there.

#### Common questions <a href="#common-questions" id="common-questions"></a>

**I can't find the file in Media → Library.** That's expected. Open it from the submission or the order instead — see [Where to find an uploaded file](#where-to-find-an-uploaded-file).

**The order shows a filename but no link.** Orders placed before version 1.8.1 has added the fix can be repaired in place. Ask your developer to run `wp craftforms backfill-files --dry-run` to preview, then without the flag to apply. New orders are unaffected.

**A customer forwarded their download link to someone else.** That person can now open that one file. Links carry no session and give no access to anything else — no other file, no order, no account. If a specific file needs locking down, delete it: remove the submission (and the order, if there is one) and the link stops working immediately.

**Can I have uploads in the Media Library anyway?** Not through a setting, deliberately — it would undo the protections above for every upload on the site. If a specific integration needs a real attachment (a packing-slip plugin, say), that's a job for a developer hook rather than a site-wide switch.

**Are uploads included in my backups?** Yes. They're inside `wp-content/uploads` like everything else, so any backup that covers your uploads folder covers them. Keep the `.json` files next to the uploads — they hold the real filenames and the access keys.

**Can I move the folder somewhere else?** Not by moving it by hand — the links would break. It follows your WordPress uploads directory, so if you relocate that, CraftForms follows.
