PHP File Upload
In today's digital age, file uploads are a common requirement in web development. Whether it's a profile picture, a document, or any other file type, the
File uploads are a common requirement in web development. Whether it's a profile picture, a PDF document, or a CSV import, letting users send files to your server is a core feature of most applications. In PHP this is handled by the move_uploaded_file() function together with the $_FILES superglobal array.
This chapter covers the full upload flow: configuring the HTML form, reading the uploaded file's metadata from $_FILES, validating it safely, and moving it to its final destination. It also walks through the upload error codes and the PHP configuration settings that control upload limits.
How a PHP file upload works
A file upload happens in three stages:
- The browser sends the file in a
multipart/form-dataPOST request. - PHP receives the file and writes it to a temporary location on disk, then exposes details about it in the
$_FILESarray. - Your script validates the file and moves it from the temporary location to a permanent one with
move_uploaded_file().
The temporary file is deleted automatically at the end of the request if you don't move it, so you must process it during the same request.
The $_FILES superglobal
When a file is uploaded, the information about it is stored in the $_FILES superglobal array. For a form field named userfile, the array contains these keys:
$_FILES['userfile']['name']- The original name of the uploaded file.$_FILES['userfile']['type']- The MIME type of the uploaded file.$_FILES['userfile']['size']- The size of the uploaded file in bytes.$_FILES['userfile']['tmp_name']- The temporary location of the uploaded file on the server.$_FILES['userfile']['error']- An error code indicating if there was an issue during the file upload (see the error codes below).
Step 1: The HTML form
The form's enctype attribute must be set to multipart/form-data, and the method must be POST. Without multipart/form-data, the browser sends only the file name, not its contents, and $_FILES will be empty.
<form action="upload.php" method="POST" enctype="multipart/form-data">
<input type="file" name="userfile">
<input type="submit" value="Upload">
</form>Step 2: Validate the upload
Never trust an uploaded file. Before moving it, check three things: that the upload itself succeeded, that the size is within your limit, and that the file is really the type you expect.
Check the error code
Always inspect $_FILES['userfile']['error'] first. PHP defines named constants for the possible values:
| Constant | Value | Meaning |
|---|---|---|
UPLOAD_ERR_OK | 0 | No error, the file uploaded successfully. |
UPLOAD_ERR_INI_SIZE | 1 | File exceeds upload_max_filesize in php.ini. |
UPLOAD_ERR_FORM_SIZE | 2 | File exceeds the form's MAX_FILE_SIZE field. |
UPLOAD_ERR_PARTIAL | 3 | The file was only partially uploaded. |
UPLOAD_ERR_NO_FILE | 4 | No file was uploaded. |
UPLOAD_ERR_NO_TMP_DIR | 6 | Missing a temporary folder. |
UPLOAD_ERR_CANT_WRITE | 7 | Failed to write the file to disk. |
Validate type and size securely
Do not rely on $_FILES['userfile']['type']. That value is supplied by the browser and is trivially spoofed by an attacker. Instead, detect the real MIME type from the file's contents with the finfo extension, and enforce a size limit yourself:
$file = $_FILES['userfile'];
// 1. Did the upload succeed?
if ($file['error'] !== UPLOAD_ERR_OK) {
exit("Upload failed with error code " . $file['error']);
}
// 2. Enforce a maximum size (2 MB here).
$maxBytes = 2 * 1024 * 1024;
if ($file['size'] > $maxBytes) {
exit("File is too large.");
}
// 3. Detect the real MIME type, not the client-supplied one.
$finfo = new finfo(FILEINFO_MIME_TYPE);
$mime = $finfo->file($file['tmp_name']);
$allowed = [
'image/jpeg' => 'jpg',
'image/png' => 'png',
'image/gif' => 'gif',
];
if (!isset($allowed[$mime])) {
exit("Only JPEG, PNG, and GIF images are allowed.");
}Step 3: Move the file to its final destination
Once the file is validated, move it with move_uploaded_file(). It takes two arguments: the temporary path ($_FILES['userfile']['tmp_name']) and the target path. Using this function rather than copy() or rename() is important — it verifies the file was a genuine HTTP upload, which blocks an attacker from tricking your script into moving an arbitrary server file.
Generate the final file name yourself instead of trusting the original name. This avoids directory-traversal attacks (a name like ../../config.php) and overwriting existing files:
$targetDir = "uploads/";
// Build a safe, unique file name; never trust the client's name.
$extension = $allowed[$mime];
$safeName = bin2hex(random_bytes(8)) . "." . $extension;
$targetFile = $targetDir . $safeName;
if (move_uploaded_file($file['tmp_name'], $targetFile)) {
echo "The file was uploaded as " . $safeName;
} else {
echo "There was an error saving the file.";
}For more detail on this function and its companion is_uploaded_file(), see the move_uploaded_file() and is_uploaded_file() reference chapters.
Configuration that affects uploads
A few php.ini directives silently cap what your script can receive. If uploads fail for large files even though your code is correct, check these:
file_uploads— must beOnfor uploads to work at all.upload_max_filesize— the largest individual file PHP will accept (default2M).post_max_size— the largest total POST body; must be larger thanupload_max_filesize.max_file_uploads— the maximum number of files in a single request.
Conclusion
PHP file uploads are a fundamental part of web development. With the $_FILES superglobal array and the move_uploaded_file() function, you can handle uploads in just a few lines. The hard part is doing it safely: always check the upload error code, enforce a size limit, detect the real MIME type with finfo instead of trusting $_FILES[...]['type'], and generate your own file name so the client can never control where the file lands. For processing the saved file afterwards, see PHP file handling and form validation.