File Upload
Learn how to build an upload form and handle uploaded files in PHP using the $_FILES superglobal.
Introduction
Profile pictures, resumes, product images, PDF attachments — almost every real web application eventually needs to let a visitor upload a file. PHP makes this possible through a special superglobal called $_FILES, combined with a form that is configured just a little differently from the ones you have used so far.
This lesson walks through building an upload form, reading the uploaded file's details, saving it to the server with move_uploaded_file(), and validating it so your application only accepts safe, expected files.
- How to build an HTML form that can send files, using enctype="multipart/form-data".
- How to read uploaded file details through the $_FILES superglobal.
- How to save an uploaded file permanently with move_uploaded_file().
- How to validate file size and type before accepting an upload.
- Why upload validation is a critical security concern.
Building an Upload Form
A regular HTML form cannot send file contents by default. To upload a file, the form needs method="post" and a special enctype="multipart/form-data" attribute, plus an <input type="file"> field.
<form action="upload.php" method="post" enctype="multipart/form-data">
<label for="avatar">Choose a file:</label>
<input type="file" name="avatar" id="avatar">
<button type="submit">Upload</button>
</form>Without enctype="multipart/form-data", the browser will submit only the file name as plain text — the actual file contents never reach the server.
The $_FILES Superglobal
When a file is uploaded, PHP places its details into the $_FILES superglobal, keyed by the input's name attribute. Each entry is an array describing the uploaded file.
<?php
print_r($_FILES["avatar"]);
?>Array
(
[name] => photo.jpg
[type] => image/jpeg
[tmp_name] => /tmp/phpA1B2C3
[error] => 0
[size] => 204800
)| Key | Meaning |
|---|---|
| name | The original file name on the visitor's computer, e.g. photo.jpg. |
| type | The MIME type reported by the browser, e.g. image/jpeg — not fully trustworthy on its own. |
| tmp_name | The temporary path where PHP stored the file on the server during the request. |
| error | An error code; 0 means the upload succeeded with no errors. |
| size | The file size in bytes. |
Moving the Uploaded File
The file at tmp_name is temporary and gets deleted automatically once the request finishes. To keep it, use move_uploaded_file() to move it from the temporary location to a permanent folder on the server.
<?php
$destination = "uploads/" . basename($_FILES["avatar"]["name"]);
if (move_uploaded_file($_FILES["avatar"]["tmp_name"], $destination)) {
echo "File uploaded successfully to " . $destination;
} else {
echo "Upload failed.";
}
?>File uploaded successfully to uploads/photo.jpgbasename() strips any directory information from the submitted file name, which helps prevent a malicious file name from writing outside the intended uploads folder.
Validating Uploads
Never trust an uploaded file blindly. At minimum, check the file size against a limit and restrict which extensions or MIME types are allowed before moving the file anywhere.
<?php
$file = $_FILES["avatar"];
$maxSize = 2 * 1024 * 1024; // 2 MB
$allowedTypes = ["image/jpeg", "image/png", "image/gif"];
if ($file["size"] > $maxSize) {
echo "File is too large. Max size is 2MB.";
} elseif (!in_array($file["type"], $allowedTypes)) {
echo "Only JPEG, PNG, and GIF images are allowed.";
} else {
$destination = "uploads/" . basename($file["name"]);
move_uploaded_file($file["tmp_name"], $destination);
echo "Upload accepted.";
}
?>Only JPEG, PNG, and GIF images are allowed.Why Validation Matters
File uploads are one of the most common ways attackers try to compromise a web server. Without validation, a visitor could upload an executable script disguised as an image and, if it ends up somewhere the server will run it, gain control of the application.
Enforce Size Limits
Reject files larger than your application actually needs to prevent storage abuse.
Check the Extension
Compare the file extension against an explicit allow-list, never a block-list.
Verify the MIME Type
The type field is reported by the browser and can be spoofed — for stronger checks, PHP's finfo functions inspect the actual file content.
Store Outside the Web Root When Possible
Keeping uploads out of a publicly executable folder reduces the risk of an uploaded script ever running.
Example: A Complete Upload Script
Putting it all together: build the form, check for upload errors, validate size and type, and only then move the file.
<?php
if ($_SERVER["REQUEST_METHOD"] === "POST" && isset($_FILES["avatar"])) {
$file = $_FILES["avatar"];
if ($file["error"] !== UPLOAD_ERR_OK) {
echo "Upload error occurred.";
} elseif ($file["size"] > 2 * 1024 * 1024) {
echo "File is too large.";
} elseif (!in_array($file["type"], ["image/jpeg", "image/png"])) {
echo "Only JPEG or PNG files are allowed.";
} else {
$destination = "uploads/" . basename($file["name"]);
if (move_uploaded_file($file["tmp_name"], $destination)) {
echo "Uploaded: " . $destination;
} else {
echo "Could not save the file.";
}
}
}
?>Uploaded: uploads/photo.jpgCommon Mistakes
- Forgetting enctype="multipart/form-data" on the form — the file never actually arrives.
- Trusting $_FILES["type"] alone to determine what a file really is.
- Skipping the error field and assuming every upload succeeded.
- Using the original file name directly without basename(), risking directory traversal.
- Not setting any size limit, allowing huge files to fill server storage.
Best Practices
- Always check $_FILES["field"]["error"] before doing anything else with the upload.
- Validate both file size and an allow-list of extensions or MIME types.
- Sanitize the destination file name with basename() and consider generating a new unique name.
- Store uploads outside the publicly accessible web root when the application allows it.
- Set a reasonable upload_max_filesize and post_max_size in php.ini to match your application's needs.
Frequently Asked Questions
Why does my $_FILES array come back empty?
The most common cause is a missing enctype="multipart/form-data" attribute on the form, or a file that exceeds php.ini's upload_max_filesize.
Can I upload more than one file at once?
Yes — name the input like avatar[] and $_FILES["avatar"] will contain arrays of names, types, tmp_names, errors, and sizes instead of single values.
Is checking the file extension enough for security?
No. Extensions and reported MIME types can both be faked, so combine them with size limits, storage location choices, and, for stricter needs, content inspection.
What does error code 1 mean in $_FILES?
It means the uploaded file exceeds the upload_max_filesize directive set in php.ini.
Do I need to create the uploads folder myself?
Yes, move_uploaded_file() will not create missing directories — the destination folder must already exist and be writable by the web server.
Key Takeaways
- File uploads require method="post" and enctype="multipart/form-data" on the form.
- Uploaded file details are available through the $_FILES superglobal.
- move_uploaded_file() saves the file from its temporary location to a permanent one.
- Always validate file size and type before accepting an upload.
- Unvalidated uploads are a common and serious security risk.
Summary
Handling file uploads combines a specially configured HTML form with PHP's $_FILES superglobal and the move_uploaded_file() function. Because uploads accept arbitrary data from visitors, validating size and type before saving anything is essential.
In this lesson, you learned how to build an upload form, inspect $_FILES, move an uploaded file, and validate it safely. Next, you will explore file handling more broadly — reading and writing plain files on the server.
- You can build a working file upload form.
- You understand every field in the $_FILES superglobal.
- You can save uploads with move_uploaded_file().
- You know how to validate uploads for size and type.