$_FILES

Yo Yogesh.M Updated 01 Nov 2026
7 min read ·Lesson 218 of 219

What Is $_FILES?

In simple terms: $_FILES contains information about files a user uploads through a form — the original file name, its size, where PHP temporarily stored it, and whether the upload succeeded. Technically: $_FILES is a superglobal populated when a POST request is sent with Content-Type: multipart/form-data. For each file input, PHP saves the uploaded file to a temporary location and describes it in $_FILES. Unless your script moves the file somewhere permanent, PHP deletes it automatically when the request ends.

Why Uploads Need Special Care

File uploads are among the most security-sensitive features of a web application. An attacker who can upload a PHP script into a public folder and then request it can run any code on your server. Every upload must therefore be validated and stored safely — this lesson covers both.


The Upload Form

<form method="post" action="/upload.php" enctype="multipart/form-data">
    <input type="file" name="avatar" accept="image/png, image/jpeg">
    <button type="submit">Upload</button>
</form>

Three things are required:

  1. method="post" — files cannot be sent with GET.
  2. enctype="multipart/form-data" — without it, the browser sends only the file name, and $_FILES stays empty.
  3. A name attribute on the file input — it becomes the key in $_FILES. The accept attribute only filters the browser's file picker; it provides no security, because requests can be sent without the browser.

The Structure of $_FILES

After a user uploads profile photo.jpg, $_FILES looks like this:

[
    'avatar' => [
        'name'      => 'profile photo.jpg',      // original name from the client
        'full_path' => 'profile photo.jpg',      // path within an uploaded folder (PHP 8.1+)
        'type'      => 'image/jpeg',             // MIME type claimed by the client
        'tmp_name'  => '/tmp/phpA1b2C3',         // temporary file on the server
        'error'     => 0,                         // UPLOAD_ERR_OK
        'size'      => 52341,                     // size in bytes
    ],
]
KeyMeaningTrustworthy?
nameOriginal file nameNo — chosen by the client
full_pathRelative path when a directory is uploaded (PHP 8.1+)No
typeMIME type sent by the browserNo — easily faked
tmp_namePath of the temporary file on the serverYes (set by PHP)
errorUpload status codeYes
sizeSize in bytesYes (measured by PHP)

Only tmp_name, error, and size are determined by PHP. Never use name or type for security decisions.


Upload Error Codes

Always check error first:

ConstantValueMeaning
UPLOAD_ERR_OK0Upload succeeded
UPLOAD_ERR_INI_SIZE1File exceeds upload_max_filesize in php.ini
UPLOAD_ERR_FORM_SIZE2File exceeds the form's MAX_FILE_SIZE field
UPLOAD_ERR_PARTIAL3File was only partially uploaded
UPLOAD_ERR_NO_FILE4No file was selected
UPLOAD_ERR_NO_TMP_DIR6Server has no temporary folder
UPLOAD_ERR_CANT_WRITE7Server failed to write the file
UPLOAD_ERR_EXTENSION8A PHP extension stopped the upload

Turning Codes Into Messages

<?php
function uploadErrorMessage(int $code): ?string
{
    return match ($code) {
        UPLOAD_ERR_OK => null,
        UPLOAD_ERR_INI_SIZE, UPLOAD_ERR_FORM_SIZE => 'The file is too large.',
        UPLOAD_ERR_PARTIAL => 'The upload was interrupted. Please try again.',
        UPLOAD_ERR_NO_FILE => 'Please choose a file.',
        default => 'The server could not save the file. Please try later.',
    };
}
foreach ([0, 1, 4, 7] as $code) {
    var_dump(uploadErrorMessage($code));
}

Output

NULL
string(22) "The file is too large."
string(21) "Please choose a file."
string(53) "The server could not save the file. Please try later."

Server-side problems (codes 6, 7, 8) should also be logged, because they indicate a configuration issue rather than a user mistake.


Validating and Saving an Upload

Step by Step

  1. Check the error code.
  2. Check the size against your own limit.
  3. Detect the real file type from the file contents with finfo — not from type or the extension.
  4. Generate a new file name on the server; never reuse the client's name.
  5. Move the file with move_uploaded_file(), which also verifies that the file really came from an upload.

Example: Avatar Upload

<?php
// upload.php
function saveAvatar(array $file, string $targetDir): string
{
    if (!isset($file['error']) || is_array($file['error'])) {
        throw new RuntimeException('Invalid upload.');
    }
    if ($file['error'] !== UPLOAD_ERR_OK) {
        throw new RuntimeException(uploadErrorMessage($file['error']));
    }
    $maxBytes = 2 * 1024 * 1024; // 2 MB
    if ($file['size'] > $maxBytes) {
        throw new RuntimeException('The file must be 2 MB or smaller.');
    }
    $allowed = ['image/jpeg' => 'jpg', 'image/png' => 'png', 'image/webp' => 'webp'];
    $mime = (new finfo(FILEINFO_MIME_TYPE))->file($file['tmp_name']);
    if (!isset($allowed[$mime])) {
        throw new RuntimeException('Only JPEG, PNG, and WebP images are allowed.');
    }
    $fileName = bin2hex(random_bytes(16)) . '.' . $allowed[$mime];
    $destination = rtrim($targetDir, '/') . '/' . $fileName;
    if (!move_uploaded_file($file['tmp_name'], $destination)) {
        throw new RuntimeException('The server could not save the file.');
    }
    return $fileName;
}
if (($_SERVER['REQUEST_METHOD'] ?? 'GET') === 'POST') {
    try {
        $saved = saveAvatar($_FILES['avatar'] ?? [], __DIR__ . '/../storage/avatars');
        echo 'Uploaded as ' . htmlspecialchars($saved);
    } catch (RuntimeException $e) {
        http_response_code(422);
        echo htmlspecialchars($e->getMessage());
    }
}

Output After Uploading a Valid JPEG (the name is random each time)

Uploaded as 3f9a1c0e8b7d4a2e9c6b1f0a5d8e7c42.jpg

Why Each Step Matters

StepProtects Against
is_array($file['error']) checkRequests that send several files where one was expected (avatar[])
Error code checkProcessing incomplete or missing files
Size limitDisk exhaustion and oversized images
finfo MIME detectionFiles with a fake extension or fake type, such as shell.php renamed to photo.jpg
Extension from your allow-listSaving a file with an executable extension like .php
Random file nameOverwriting other files, path traversal (../../index.php), and guessing file names
move_uploaded_file()Moving files that did not come from an upload
Storage outside the web root (storage/)Direct execution of uploaded files through a URL

finfo requires the fileinfo extension, which is enabled by default. For images, re-encoding them (for example with the GD or Imagick extension) removes hidden content and metadata, adding another layer of safety.

Serving Stored Files

Files stored outside the web root are delivered through a PHP script that checks permissions and sends the file:

<?php
// avatar.php?file=3f9a1c0e8b7d4a2e9c6b1f0a5d8e7c42.jpg
$file = basename($_GET['file'] ?? '');
$path = __DIR__ . '/../storage/avatars/' . $file;
if (!preg_match('/^[a-f0-9]{32}\.(jpg|png|webp)$/', $file) || !is_file($path)) {
    http_response_code(404);
    exit;
}
header('Content-Type: ' . (new finfo(FILEINFO_MIME_TYPE))->file($path));
header('X-Content-Type-Options: nosniff');
readfile($path);

The strict pattern accepts only file names in the format your upload code creates, which prevents path traversal. If uploads must be stored inside the public folder, configure the web server to never execute scripts in that folder.


Multiple Files

With name="photos[]" and the multiple attribute, users can select several files:

<input type="file" name="photos[]" multiple>

PHP organizes the result by property, not by file:

$_FILES['photos'] = [
    'name'     => ['beach.jpg', 'sunset.png'],
    'type'     => ['image/jpeg', 'image/png'],
    'tmp_name' => ['/tmp/phpA1', '/tmp/phpB2'],
    'error'    => [0, 0],
    'size'     => [52341, 81022],
];

A helper converts this into one array per file:

<?php
function normalizeFiles(array $files): array
{
    if (!is_array($files['name'] ?? null)) {
        return [$files];
    }
    $normalized = [];
    foreach (array_keys($files['name']) as $index) {
        foreach ($files as $property => $values) {
            $normalized[$index][$property] = $values[$index];
        }
    }
    return $normalized;
}
$example = [
    'name' => ['beach.jpg', 'sunset.png'],
    'type' => ['image/jpeg', 'image/png'],
    'tmp_name' => ['/tmp/phpA1', '/tmp/phpB2'],
    'error' => [0, 0],
    'size' => [52341, 81022],
];
foreach (normalizeFiles($example) as $file) {
    echo "{$file['name']}: {$file['size']} bytes, error {$file['error']}\n";
}

Output

beach.jpg: 52341 bytes, error 0
sunset.png: 81022 bytes, error 0

Each normalized file can then be passed to the same validation function used for single uploads. Limit the number of files you accept; PHP's max_file_uploads setting (default 20) is an upper bound.


Configuration Limits

php.ini SettingDefaultMeaning
file_uploadsOnEnables uploads
upload_max_filesize2MMaximum size per file
post_max_size8MMaximum size of the whole request; must be larger than upload_max_filesize
max_file_uploads20Maximum number of files per request
upload_tmp_dirSystem defaultTemporary folder

If the whole request exceeds post_max_size, both $_FILES and $_POST are empty — the error codes above are never set. Check for this case explicitly, for example when REQUEST_METHOD is POST but $_FILES is empty. Web servers have their own limits too (such as Nginx's client_max_body_size).


Common Mistakes

Forgetting enctype="multipart/form-data"

Effect: $_FILES is empty. Fix: Add the attribute to the form.

Trusting name, type, or the Extension

Effect: Attackers upload executable scripts disguised as images. Fix: Detect the MIME type with finfo and choose the extension from an allow-list.

Storing Uploads in a Public, Executable Folder

Effect: Uploaded scripts can be executed through a URL. Fix: Store outside the web root, or disable script execution in the upload folder.

Keeping the Original File Name

Effect: Overwritten files, path traversal, and leaked personal information in names. Fix: Generate random names; store the original name in the database if you need to display it.

Using copy() or rename() Instead of move_uploaded_file()

Effect: No check that the file came from an upload. Fix: Always use move_uploaded_file().


Best Practices

  • Check the error code, size, and real MIME type of every upload.
  • Generate server-side file names and use extensions from an allow-list.
  • Store uploads outside the web root and serve them through a controlled script.
  • Normalize multi-file uploads and validate each file individually.
  • Align upload_max_filesize, post_max_size, and web server limits with your application's needs. Official reference: https://www.php.net/manual/en/reserved.variables.files.php

0 Comments

Reviewed before they appear

No comments yet.

PHP
PHP Home PHP Switch PHP Match Alternative Syntax PHP Loops While Loop Do While Loop For Loop Foreach Loop Nested Loops Break Statement PHP Intro PHP Install PHP Built-in Web Server PHP Syntax PHP Tags PHP and HTML PHP Comments PHP Multiline Comments PHP Variables Variables Scope PHP Casting Logical Operators Null Coalescing Operator Spaceship Operator Operator Precedence Continue Statement Modify Strings PHP If...Else...Elseif PHP If Modify Strings PHP If Operators PHP Functions Concatenate Strings PHP If...Else User-defined Functions Slicing Strings Function Arguments PHP Shorthand if Escape Characters Default Argument Values PHP Nested if Pass by Reference String Interpolation Named Arguments PHP File Upload Variadic Functions Return Values Search in Strings Multibyte Strings Type Declarations in Functions PHP Numbers PHP Math PHP Floats PHP Random Numbers PHP Constants PHP Properties PHP Methods PHP Constructor Constructor Property Promotion PHP Cookies PHP Destructor PHP Access Modifiers Readonly Properties PHP Sessions PHP Inheritance Method Overriding PHP Filters The final Keyword CSRF Protection PHP Filters Advanced Session Security Secure Cookies PHP Magic Constants File Upload Security PHP Callback Functions PHP Operators Filesystem Security PHP JSON Arithmetic Operators Error Reporting in Production Hiding PHP Assignment Operators PHP Configuration (php.ini) Comparison Operators PHP Command Line (CLI) PHP Exceptions Increment / Decrement Operators Migrating to PHP 8 PHP Form Validation PHP Form Required PHP Form URL/E-mail Error Handling Form Input Sanitizing Escaping Output (htmlspecialchars) Custom Error Handler Checkbox, Radio and Select Inputs Sticky Forms PHP Form Complete PHP Date and Time Date Formatting Error Logging HTTP Headers and Redirects Output Buffering Sending Email PHP cURL Consuming REST APIs PHP Generators AJAX PHP AJAX Database AJAX XML AJAX Live Search AJAX Poll AJAX with JSON Security Introduction Password Hashing SQL Injection Prevention XSS Prevention Garbage Collection Project: Simple Blog PHP What is OOP PHP Classes/Objects The $this Keyword Access Array Items Update Array Items Add Array Items Remove Array Items Sorting Arrays Multidimensional Arrays Array Destructuring Array Spread Operator Search in Arrays Merge Arrays PHP Abstract Classes PHP Interfaces Polymorphism PHP Traits PHP Static Methods PHP Static Properties Late Static Binding Magic Methods Object Cloning MySQL Database MySQL Connect MySQL Create DB MySQL Create Table MySQL Insert Data MySQL Get Last ID MySQL Insert Multiple MySQL Prepared MySQL Select Data MySQL Where Debugging PHP Project: Image Upload Gallery $_SESSION $_ENV PHP RegEx RegEx Patterns Heredoc and Nowdoc RegEx Modifiers preg_match preg_replace preg_split PHP RegEx Functions Nullable Types Mixed and Never Types Nullsafe Operator PHP Attributes PHP Fibers Readonly Classes Property Hooks (PHP 8.4) Asymmetric Visibility (PHP 8.4) Reflection API SPL Data Structures Composer PHP Form Handling PHP XML Parsers PDO Transactions PDO Error Handling PHP XML Parsers PHP SimpleXML Parser PHP SimpleXML - Get PHP XML Expat Parser PHP DOM Parser PHP XMLReader PHP XMLWriter AJAX Intro PHP Class Constants Encryption with Sodium Recursive Functions Variable Functions Anonymous Functions Closures Arrow Functions First-class Callable Syntax PHP Arrays Indexed Arrays Associative Arrays Create Arrays Variables Scope Static Variables Variable Variables PHP Echo / Print PHP printf / sprintf PHP Data Types PHP Booleans PHP NULL PHP Strings String Functions array_map / array_filter / array_reduce Array Functions What Are Superglobals? $GLOBALS What Is `$_SERVER` ? $_REQUEST $_POST $_GET $_FILES $_COOKIE
Ask about this post
AI Ask about this post

Ask questions about $_FILES and get answers drawn from it.

Signed-in readers only.