File upload allows receiving a file on the server sent via an HTTP form.
Uploading files involves two parts: the HTML form that sends the file, and the PHP script that receives it, validates it, and moves it to its final location.
The HTML Form (the forgotten requirement)
To upload files, the <form> tag must have two mandatory attributes. If you forget one, the file will never reach PHP.
method="POST": Files don’t travel via GET.enctype="multipart/form-data": Tells the browser to build a request capable of including files.
<form action="upload.php" method="POST" enctype="multipart/form-data">
<label>Select an image:</label>
<input type="file" name="user_file">
<button type="submit">Upload</button>
</form>The $_FILES Superglobal
When PHP receives the file, it doesn’t put it in $_POST. It places it in $_FILES. It’s a two-dimensional array with 5 key data points for each uploaded file:
If your input is named name="user_file", you’ll have:
| Index | Description | Importance |
|---|---|---|
$_FILES['user_file']['name'] | The original name (e.g., photo.jpg). | Dangerous: The user can manipulate it. |
$_FILES['user_file']['type'] | The file type (e.g., image/jpeg). | Dangerous: The browser can lie. |
$_FILES['user_file']['tmp_name'] | Where PHP stored it temporarily. | This is the path we’ll use to validate and move it. |
$_FILES['user_file']['error'] | Error code (0 = Success). | You must verify it’s 0. |
$_FILES['user_file']['size'] | Size in bytes. | Use it to validate limits. |
The Security Process (Step by Step)
Never move the file directly. Follow this security checklist:
Verify Errors: Check if ['error'] === UPLOAD_ERR_OK.
Validate Size: Don’t allow them to fill your hard drive.
Validate Type (MIME): Ensure it’s a real image and not a disguised .php script.
Rename the File: Never save the file with the original name.
- Risk 1: Overwriting (two users upload
photo.jpg). - Risk 2: Hacking (a user uploads
hack.phpand then tries to execute it). - Solution: Generate a random name with
random_bytes()and assign the extension based on the verified MIME.
Complete and Secure Example
This script implements all the security barriers mentioned.
// Configuration
$uploadDirectory = 'uploads/';
$maxSize = 2 * 1024 * 1024; // 2 MB
// Ensure the file was sent
if ($_SERVER['REQUEST_METHOD'] == 'POST' && isset($_FILES['user_file'])) {
$file = $_FILES['user_file'];
// 1. Check upload errors
if ($file['error'] !== UPLOAD_ERR_OK) {
die("Error uploading the file.");
}
// 2. Validate size
if ($file['size'] > $maxSize) {
die("Error: File is too large (Max 2MB).");
}
// 3. Validate file type (Real Security)
// Don't trust $file['type'], use finfo to check the actual content
$finfo = new finfo(FILEINFO_MIME_TYPE);
$mimeType = $finfo->file($file['tmp_name']);
$allowedFormats = [
'image/jpeg' => 'jpg',
'image/png' => 'png',
'application/pdf' => 'pdf',
];
if (!isset($allowedFormats[$mimeType])) {
die("Error: File format not allowed.");
}
// 4. Generate name and extension from trusted data
$extension = $allowedFormats[$mimeType];
$newName = bin2hex(random_bytes(16)) . '.' . $extension;
$destinationPath = $uploadDirectory . $newName;
// 5. Move from temporary to final location
if (move_uploaded_file($file['tmp_name'], $destinationPath)) {
echo "Success! File saved as: " . $newName;
} else {
echo "Error moving the file.";
}
}Save uploads outside the public directory whenever possible. If they must be public, configure the server to prevent script execution in that folder and serve the files with the appropriate headers.
If you try to upload a large file and it fails without an error, check your php.ini file. The upload_max_filesize and post_max_size directives are often limited to 2MB by default.