Skip to content

PHP Upload Error Codes Explained

Every entry in $_FILES carries an error value, and reading it correctly turns "the upload did not work" into a specific, fixable cause. There are eight defined constants, one gap in the numbering, and several failure modes that produce no code at all.

The codes at a glance

ConstantValueMeaning
UPLOAD_ERR_OK0The file uploaded successfully.
UPLOAD_ERR_INI_SIZE1Larger than upload_max_filesize.
UPLOAD_ERR_FORM_SIZE2Larger than the form's MAX_FILE_SIZE hidden field.
UPLOAD_ERR_PARTIAL3Only part of the file arrived.
UPLOAD_ERR_NO_FILE4No file was submitted.
UPLOAD_ERR_NO_TMP_DIR6The temporary folder is missing.
UPLOAD_ERR_CANT_WRITE7Writing to disk failed.
UPLOAD_ERR_EXTENSION8A PHP extension stopped the upload.

There is no error 5. The value was used briefly during PHP 5.x development and then withdrawn, and the gap has been there ever since. If you are switching on these values, do not write a case for 5 — and if something in your logs reports 5, it is not coming from PHP's upload handling.

What each one actually means

1 — UPLOAD_ERR_INI_SIZE

The file exceeded upload_max_filesize (default 2M). The upload is rejected per-file, so in a batch the other files can still succeed. Raise the directive, remembering that post_max_size must stay comfortably above it — see the size limits article.

2 — UPLOAD_ERR_FORM_SIZE

The file exceeded a MAX_FILE_SIZE hidden field in the form:

<input type="hidden" name="MAX_FILE_SIZE" value="2097152">

This is a client-side courtesy only — the browser is trusted to honour it, and anyone can edit or remove it. Treat code 2 as a hint, never as a security control, and enforce the real limit server-side.

3 — UPLOAD_ERR_PARTIAL

The connection ended before the whole file arrived: the user navigated away, the network dropped, a proxy timed out, or the browser was closed mid-transfer. There is nothing to fix in configuration — discard the fragment and ask the user to retry. Frequent code 3 on large files is the signal to move to chunked uploads, where only the failed chunk is repeated.

4 — UPLOAD_ERR_NO_FILE

The field was submitted with nothing chosen. Perfectly normal for an optional input, so treat it as "skip this one" rather than an error:

if ($file['error'] === UPLOAD_ERR_NO_FILE) {
    continue;   // optional field, nothing selected
}

6 — UPLOAD_ERR_NO_TMP_DIR

PHP has nowhere to put the incoming file. Either upload_tmp_dir points somewhere that does not exist, or it is unset and the system temp directory is unavailable — common in hardened or containerised environments, and on Windows where the path may not exist for the service account. Check what PHP thinks:

var_dump(ini_get('upload_tmp_dir'), sys_get_temp_dir(), is_writable(sys_get_temp_dir()));

7 — UPLOAD_ERR_CANT_WRITE

The temp directory exists but the write failed: permissions, a full disk, a quota, or SELinux/AppArmor blocking the web-server user. Check free space first — a full /tmp is the usual culprit and produces no other symptom.

8 — UPLOAD_ERR_EXTENSION

A loaded PHP extension aborted the upload. It is deliberately vague, because PHP does not record which one. Realistically it is a security or antivirus module; check the extensions list and the error log around the same timestamp.

Turning codes into messages

function upload_error_message(int $code): string
{
    return match ($code) {
        UPLOAD_ERR_OK         => 'Uploaded successfully.',
        UPLOAD_ERR_INI_SIZE   => 'The file is larger than this server allows.',
        UPLOAD_ERR_FORM_SIZE  => 'The file is larger than the form allows.',
        UPLOAD_ERR_PARTIAL    => 'The upload was interrupted. Please try again.',
        UPLOAD_ERR_NO_FILE    => 'No file was selected.',
        UPLOAD_ERR_NO_TMP_DIR => 'Server error: no temporary folder.',
        UPLOAD_ERR_CANT_WRITE => 'Server error: could not write the file.',
        UPLOAD_ERR_EXTENSION  => 'The upload was blocked by a server extension.',
        default               => 'Unknown upload error (' . $code . ').',
    };
}

Codes 6, 7 and 8 are server faults, not user mistakes. Log those with context and show a generic apology — the user cannot act on "no temporary folder", and the detail belongs in your log, not on the page. Codes 1 to 4 are worth stating plainly, because the user genuinely can retry with a smaller file.

On PHP versions before 8.0, replace match with a switch. The constants themselves have been stable since PHP 5.2, so the logic is otherwise unchanged.

Failures that produce no error code at all

The most confusing upload bugs are the ones where there is nothing in $_FILES to inspect:

A defensive handler checks for these before iterating:

if ($_SERVER['REQUEST_METHOD'] === 'POST' && empty($_FILES)) {
    $len = (int) ($_SERVER['CONTENT_LENGTH'] ?? 0);
    if ($len > 0) {
        error_log("Upload discarded: {$len} bytes exceeded post_max_size");
        // report a size error to the user
    } else {
        // form was posted without a file field, or without the right enctype
    }
}

Fewer of these to handle

Most of the codes above describe limits that only exist because the whole file rides in one POST. PHP File Uploader validates size and type in the browser before transferring anything, streams the data in parts rather than one request, and reports a specific message per file — so users see "too large" the moment they pick the file, not after a two-minute wait. The validation demo shows the behaviour, and the API documentation lists the server-side events.