skip to content

In PHP, which UPLOAD_ERR_* codes can $_FILES report, and what does each tell an upload handler to do?

level: middleimportance: should knowfreq 45%

answer

  1. 0 means success, and only 0
  2. INI_SIZE vs FORM_SIZE
  3. PARTIAL: the body ended early
  4. NO_FILE still creates the entry
  5. 6, 7, 8 are server-side faults

basics

~20 s

The error key holds UPLOAD_ERR_OK (0) on success, or a code for too large (INI_SIZE 1, FORM_SIZE 2), interrupted (PARTIAL 3), no file (NO_FILE 4) or a server fault (NO_TMP_DIR 6, CANT_WRITE 7, EXTENSION 8). Only 0 has a usable temp file.

solid answer

~40 s

`$_FILES['cv']['error']` is an int. `UPLOAD_ERR_OK` (0) is the only value with a usable `tmp_name`. `UPLOAD_ERR_INI_SIZE` (1) means the file passed `upload_max_filesize`; `UPLOAD_ERR_FORM_SIZE` (2) means it passed a `MAX_FILE_SIZE` hidden field placed before the file input - a client-editable hint, never a security control. `UPLOAD_ERR_PARTIAL` (3) means the part ended before its closing boundary: a truncated or malformed body. `UPLOAD_ERR_NO_FILE` (4) means no file was chosen. The next three are server faults to log and alert on, not show the user: `UPLOAD_ERR_NO_TMP_DIR` (6, the temp file could not be created), `UPLOAD_ERR_CANT_WRITE` (7, writing failed, often a full disk) and `UPLOAD_ERR_EXTENSION` (8, an extension stopped the upload). There is no code 5. On any error PHP discards the partial data, so `size` is 0.

code

php · 18 lines
php
<?php
declare(strict_types=1);

$error = $_FILES['cv']['error'] ?? UPLOAD_ERR_NO_FILE;

$problem = match ($error) {
    UPLOAD_ERR_OK => null,
    UPLOAD_ERR_INI_SIZE, UPLOAD_ERR_FORM_SIZE => 'The CV is larger than 5 MB.',
    UPLOAD_ERR_PARTIAL => 'The upload was interrupted. Please try again.',
    UPLOAD_ERR_NO_FILE => 'Please attach your CV.',
    UPLOAD_ERR_NO_TMP_DIR, UPLOAD_ERR_CANT_WRITE, UPLOAD_ERR_EXTENSION
        => 'We could not receive files right now. Please try later.',
    default => 'The upload was not understood.',
};

if (in_array($error, [UPLOAD_ERR_NO_TMP_DIR, UPLOAD_ERR_CANT_WRITE, UPLOAD_ERR_EXTENSION], true)) {
    error_log("CV upload server fault: code {$error}");
}

go deeper

for a junior

Recall that error 0 is UPLOAD_ERR_OK and that you must check the error before touching the file; know the too-large and no-file codes.

for a middle

Explain every code, which are client-caused and which are server faults, and why MAX_FILE_SIZE is only a hint.

for a senior

Treat codes 6, 7 and 8 as operational signals with logging and alerts, and recognise the post_max_size case that yields no code at all.

for a principal

Define how upload failures surface across services - user messages, metrics per code, alerts - so infrastructure faults are caught before users report them.

## Where the code comes from For every file part in a `multipart/form-data` body, PHP sets `$_FILES[<field>]['error']` to an integer while it streams the part to disk. The constants are defined by PHP core and are always available: | Constant | Value | Meaning | |---|---|---| | `UPLOAD_ERR_OK` | 0 | the file arrived completely | | `UPLOAD_ERR_INI_SIZE` | 1 | it exceeded the `upload_max_filesize` directive | | `UPLOAD_ERR_FORM_SIZE` | 2 | it exceeded the form's `MAX_FILE_SIZE` field | | `UPLOAD_ERR_PARTIAL` | 3 | the part ended before its closing boundary | | `UPLOAD_ERR_NO_FILE` | 4 | the file input was submitted empty | | `UPLOAD_ERR_NO_TMP_DIR` | 6 | PHP could not create the temporary file | | `UPLOAD_ERR_CANT_WRITE` | 7 | writing the temporary file failed | | `UPLOAD_ERR_EXTENSION` | 8 | a PHP extension stopped the upload | Value 5 is not used. When the code is anything but 0, PHP deletes whatever it had written, sets `size` to 0 and leaves `tmp_name` empty, so there is nothing to inspect or move. ## The size codes, and why `MAX_FILE_SIZE` is only a hint `UPLOAD_ERR_INI_SIZE` is authoritative: `upload_max_filesize` (default `2M`) is server configuration, checked as bytes are written. `UPLOAD_ERR_FORM_SIZE` comes from a hidden field named exactly `MAX_FILE_SIZE`, which must appear **before** the file input in the form, because PHP reads it while streaming the parts in order. It exists to let PHP stop writing an oversized file early, but the client controls the field, so anyone can raise it or remove it. The server's own limits, and your own `size` check, are what enforce a maximum. From the user's point of view both codes mean "file too large"; show the same message with the real limit. ## Client-side codes - **`UPLOAD_ERR_PARTIAL`** - the part stopped before its closing boundary: the body PHP received was truncated or malformed. A web server that buffers the whole body before handing it to PHP usually drops an aborted upload itself, so this code is uncommon; when it does appear, ask the user to retry. - **`UPLOAD_ERR_NO_FILE`** - the input was submitted without a file. The `$_FILES` entry still exists, so this code is how you detect "nothing chosen". For a required CV it is a validation error; for an optional attachment it is fine. ## Server-side codes These are not the user's fault and point to configuration or infrastructure: 1. **`UPLOAD_ERR_NO_TMP_DIR`** - despite the name, PHP sets it whenever it fails to create the temporary file: a missing directory or one the PHP user cannot write to. 2. **`UPLOAD_ERR_CANT_WRITE`** - a `write()` to the temp file failed or wrote short, most often a full disk. 3. **`UPLOAD_ERR_EXTENSION`** - an extension hooked into upload processing refused the file. PHP does not say which one; check the loaded extensions and logs. Log these with enough context to act on, alert if they repeat, and show the user a generic "try again later" message. ## Reproducing each code before production does Upload errors are rare in development, so handlers often ship with untested branches. Each code can be provoked on purpose: | Code | How to provoke it locally | |---|---| | `UPLOAD_ERR_INI_SIZE` | set `upload_max_filesize` to `100K` and upload a larger file | | `UPLOAD_ERR_FORM_SIZE` | add `MAX_FILE_SIZE` with a small value before the file input | | `UPLOAD_ERR_NO_FILE` | submit the form without choosing a file | | `UPLOAD_ERR_PARTIAL` | send a hand-built multipart body whose file part has no closing boundary | | `UPLOAD_ERR_NO_TMP_DIR` | point `upload_tmp_dir` at a directory the PHP user cannot write | Automated tests usually build the `$_FILES` array by hand for each code, because the real parsing only runs in a web request. ## Handling order - Read the entry with `$_FILES['cv'] ?? null`; a missing key means the body never carried the field, PHP skipped the part, or the whole body was discarded. - Check the error code **before** reading `tmp_name` or `size`. - Map codes to messages with a `match` on the constants; never compare against bare integers scattered through the code. - Only for `UPLOAD_ERR_OK` continue to your own checks: maximum size, real content type, then `move_uploaded_file()`. One case produces **no** code at all: when the whole request body is larger than `post_max_size`, PHP parses nothing, so `$_FILES` is empty and there is no entry to read an error from.

  • Why is a MAX_FILE_SIZE hidden field not a real size limit?
    The client sends it, so anyone can change or delete it before submitting. PHP honours it only as an early stop that spares an honest user a long wait. The enforced limits are `upload_max_filesize` and `post_max_size` in server configuration, plus the handler's own check on `$_FILES['cv']['size']`.
  • Your logs show a burst of UPLOAD_ERR_CANT_WRITE; what do you check?
    Code 7 means PHP created the temp file but a write to it failed or wrote short. Check free space and inodes on the filesystem holding `upload_tmp_dir` (or the system temp directory), quotas for the PHP user, and whether something else is filling that disk. It is a server fault, so users should see a retry message while operators are alerted.

saying these in an interview costs you the question

  • A MAX_FILE_SIZE hidden field enforces the upload size limit.
  • UPLOAD_ERR_NO_FILE means the $_FILES entry is missing.
  • An upload with UPLOAD_ERR_PARTIAL can still be moved and used.
  • UPLOAD_ERR_NO_TMP_DIR only fires when upload_tmp_dir is unset.
  • A body over post_max_size shows up as UPLOAD_ERR_INI_SIZE.