In Django, why is request.FILES empty after a user submits a form with a file input, and what must the request look like?
answer
- how the browser encodes the form
- multipart/form-data
- only POST bodies are parsed
- keyed by the input's name
basics
~10 sDjango fills request.FILES only for a POST request whose form used enctype="multipart/form-data" and actually sent a file. Without that enctype the browser sends only the file name as text, so FILES stays empty.
solid answer
~30 s`request.FILES` is populated only when three things hold: the method is `POST`, the `<form>` has `enctype="multipart/form-data"`, and at least one file field was actually sent. Without the enctype the browser URL-encodes the form and sends just the file name as a text value, which lands in `request.POST`, never in `FILES`. Django also parses request bodies into `POST` and `FILES` only for `POST`, so a `PUT` or `PATCH` upload leaves both empty. `request.FILES` is a `MultiValueDict` keyed by each input's `name` attribute, holding `UploadedFile` objects; use `request.FILES.getlist("scans")` when several files share one name. Forms need both dicts: `InvoiceForm(request.POST, request.FILES)`.
code
html · 5 lines<form method="post" enctype="multipart/form-data">
{% csrf_token %}
<input type="file" name="invoice" accept="application/pdf,image/*">
<button type="submit">Upload invoice</button>
</form>go deeper
Remember the three conditions: POST, enctype="multipart/form-data", and a named file input that was actually sent.
Explain what the browser sends without the enctype, why only POST bodies are parsed, and how MultiValueDict indexing differs from getlist().
Diagnose upload bugs from the request's Content-Type and method first, and keep upload views from reading request.body.
Decide whether browser uploads go through Django forms, an API endpoint or direct-to-storage flows, and standardise the request shape across clients.
## What request.FILES is `HttpRequest.FILES` is a dictionary-like object (a `MultiValueDict`) that maps each file input's `name` attribute to one or more `UploadedFile` objects. It is built lazily: the first time the view, a form or middleware touches `request.POST` or `request.FILES`, Django parses the request body and fills both. The Django documentation states the conditions precisely: `FILES` only contains data if 1. the request method was `POST`, 2. at least one file field was actually posted, and 3. the `<form>` that posted the request had `enctype="multipart/form-data"`. Otherwise it is an empty dictionary-like object. Each condition corresponds to a common bug. ## Bug 1: the missing enctype An HTML form without an `enctype` attribute is sent as `application/x-www-form-urlencoded`. That encoding cannot carry binary content, so the browser sends only the **file name** as an ordinary text value. The result is confusing: - `request.POST["invoice"]` contains something like `"scan-0042.pdf"`; - `request.FILES` is empty; - a Django form bound with `request.FILES` reports the file field as required and missing. The fix is one attribute: `<form method="post" enctype="multipart/form-data">`. A form rendered from a Django form class exposes `form.is_multipart()`, which templates can use to decide whether to add it. ## Bug 2: not a POST Django's request parsing runs only for `POST`. For any other method, `request.POST` and `request.FILES` are empty, even if the body is valid multipart data. A JavaScript client that uploads with `PUT` or `PATCH` therefore sees nothing in `FILES`. Either send `POST`, or parse the body yourself; API frameworks built on Django provide parsers for this. ## Bug 3: the input name The key in `request.FILES` is the input's `name`, not its `id`: | Template | View access | |---|---| | `<input type="file" name="invoice">` | `request.FILES["invoice"]` | | `<input type="file" name="scans" multiple>` | `request.FILES.getlist("scans")` | | `<input type="file" id="invoice">` (no name) | nothing is submitted | `request.FILES["scans"]` on a multi-file input returns only the **last** file, because `MultiValueDict.__getitem__` returns the last value; `getlist()` returns all of them. ## Other things that are not the cause - **CSRF.** A missing `{% csrf_token %}` produces a 403 before the view runs, not an empty `FILES`. - **Size.** A large file is still delivered; size only decides whether Django keeps it in memory or spools it to a temporary file. - **Reading `request.body`.** It does not empty `FILES`, but it pulls the whole body into memory, raises `RequestDataTooBig` for bodies over 2.5 MB, and raises `RawPostDataException` if `FILES` was already parsed from the stream. Upload views should not read `request.body`. ## Passing files to a form A bound form needs both dictionaries: `InvoiceForm(request.POST, request.FILES)`. Passing only `request.POST` is another way to end up with "this field is required" on a file that was uploaded correctly. ## Testing an upload view Django's test client posts `multipart/form-data` by default, so a test exercises the same parsing path as a browser: - build a file with `SimpleUploadedFile("scan.pdf", b"%PDF-1.7 ...", content_type="application/pdf")`, or pass an open file object; - call `client.post(url, {"invoice": upload})`, using the input's name as the key; - assert on the outcome, for example that a record was created or that a missing file gives a 400. A test that posts with `content_type="application/x-www-form-urlencoded"` reproduces the missing-enctype bug, which makes it a useful regression test for templates that build forms by hand. ## Debugging checklist 1. Inspect the request in the browser's network panel: the `Content-Type` must start with `multipart/form-data; boundary=...`. 2. Confirm the method is `POST`. 3. Confirm the input has a `name`. 4. Print `request.FILES.keys()` in the view to see what arrived. 5. Check that the form is bound with `request.FILES`.
- A JavaScript client uploads a scan with fetch() using PUT and multipart/form-data. Why is request.FILES empty?Django parses the body into `request.POST` and `request.FILES` only when the method is `POST`; for any other method both are empty regardless of content type. Switch the client to `POST`, or parse the body yourself, which API frameworks on top of Django do with their own parsers.
- With <input type="file" name="scans" multiple>, why does request.FILES["scans"] return only one file?`request.FILES` is a `MultiValueDict`, and indexing returns the last value for a key. Use `request.FILES.getlist("scans")` to get every uploaded file under that name as a list of `UploadedFile` objects.
saying these in an interview costs you the question
- Files larger than 2.5 MB never reach request.FILES
- request.FILES is filled for PUT and PATCH multipart requests too
- The key in request.FILES is the input's id attribute
- A missing csrf_token is why request.FILES is empty
- request.FILES["field"] returns all files for a multiple input