File upload is one of the common needs in many web applications — from profile photos and PDF documents to CSV files for data import. PHP provides a built-in file upload mechanism that's easy to use, but it requires extra care when it comes to security. This article will guide you in building a functional and secure file upload system from scratch.
How File Upload Works in PHP
When a user submits a form with a file, PHP receives that file as the $_FILES array. The file is temporarily stored in the server's temporary folder before we move it to the destination location using move_uploaded_file(). Important: the temporary file is automatically deleted if you don't move it right away.
Creating the Upload Form
An HTML form for file upload must have the enctype="multipart/form-data" attribute and the POST method:
<!-- upload.html -->
<form action="process_upload.php" method="POST" enctype="multipart/form-data">
<label for="photo">Choose a Photo:</label>
<input type="file" name="photo" id="photo" accept="image/*" required>
<br>
<button type="submit">Upload</button>
</form>
The accept="image/*" attribute in HTML is only a hint to the browser — it can't be relied on as a security validation. The real validation must still be done on the server side.
Processing the Upload in PHP
Create a process_upload.php file that handles receiving and storing the file:
<?php
// process_upload.php
$upload_dir = __DIR__ . '/uploads/';
// Make sure the uploads folder exists
if (!is_dir($upload_dir)) {
mkdir($upload_dir, 0755, true);
}
// Check whether the file was sent successfully
if (!isset($_FILES['photo']) || $_FILES['photo']['error'] !== UPLOAD_ERR_OK) {
$error_code = $_FILES['photo']['error'] ?? -1;
die("Upload failed. Error code: $error_code");
}
$file = $_FILES['photo'];
// 1. Validate the maximum size (2 MB)
$max_size = 2 * 1024 * 1024; // 2 MB in bytes
if ($file['size'] > $max_size) {
die("File is too large. Maximum 2 MB.");
}
// 2. Validate the allowed MIME type
$allowed_types = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
$finfo = new finfo(FILEINFO_MIME_TYPE);
$mime_type = $finfo->file($file['tmp_name']);
if (!in_array($mime_type, $allowed_types)) {
die("File type not allowed. Only JPEG, PNG, GIF, and WebP.");
}
// 3. Create a unique file name to avoid collisions
$extension = pathinfo($file['name'], PATHINFO_EXTENSION);
$new_filename = uniqid('img_', true) . '.' . strtolower($extension);
$destination = $upload_dir . $new_filename;
// 4. Move the file from temporary to the destination
if (move_uploaded_file($file['tmp_name'], $destination)) {
echo "Upload successful! File saved as: $new_filename";
} else {
echo "Failed to move the file. Check the uploads folder permissions.";
}
Understanding the $_FILES Array
When a file is uploaded, PHP fills $_FILES['input_name'] with five keys:
name— the original file name from the user's computer.type— the MIME type sent by the browser (don't fully trust it).tmp_name— the temporary file path on the server.error— the error code (0 means success /UPLOAD_ERR_OK).size— the file size in bytes.
Multiple File Upload
To allow uploading many files at once, add the multiple attribute to the input and use a field name with square brackets:
<input type="file" name="documents[]" multiple accept=".pdf,.doc,.docx">
<?php
if (isset($_FILES['documents'])) {
$files = $_FILES['documents'];
$count = count($files['name']);
for ($i = 0; $i < $count; $i++) {
if ($files['error'][$i] === UPLOAD_ERR_OK) {
$original_name = basename($files['name'][$i]);
$destination = __DIR__ . '/uploads/' . uniqid() . '_' . $original_name;
move_uploaded_file($files['tmp_name'][$i], $destination);
echo "File '$original_name' uploaded successfully.<br>";
}
}
}
Common Upload Error Codes
UPLOAD_ERR_OK (0)— success.UPLOAD_ERR_INI_SIZE (1)— the file exceedsupload_max_filesizein php.ini.UPLOAD_ERR_FORM_SIZE (2)— the file exceedsMAX_FILE_SIZEin the HTML form.UPLOAD_ERR_PARTIAL (3)— the file was only partially uploaded.UPLOAD_ERR_NO_FILE (4)— no file was selected.
File Upload Security Tips
- Always validate the MIME type using
finfo, not just the file extension or$_FILES['type']. - Use a new server-generated file name (for example with
uniqid()) — don't use the original name from the user directly. - Store files outside the
publicfolder if possible, or make sure the uploads folder can't execute PHP. - Add a
.htaccessfile in the uploads folder with the content:php_flag engine offto prevent PHP execution.
Conclusion
File upload with PHP is quite easy, but security is something you can't ignore. Always validate on the server side by checking the file size, the MIME type accurately using finfo, and use a safe file name. By following this guide, you already have an upload system that works well and is protected from potential exploits.