Letting visitors upload files (a profile picture, a document) takes three parts: a form, the PHP that receives the file and careful checks. This lesson covers the basics. The next lesson makes it safe.
The form
<form action="upload.php" method="post" enctype="multipart/form-data">
<label for="photo">Choose a photo</label>
<input type="file" id="photo" name="photo" accept="image/png, image/jpeg">
<button type="submit">Upload</button>
</form>
Both method="post" and enctype="multipart/form-data" are required. The accept attribute only hints to the browser. It does not enforce anything.
Receive the file
PHP saves the upload in a temporary folder and describes it in $_FILES (see $_FILES). You must move it to a permanent place with move_uploaded_file(). The temporary file is deleted when the script ends.
<?php
if ($_SERVER["REQUEST_METHOD"] === "POST") {
$file = $_FILES["photo"] ?? null;
if ($file === null || $file["error"] !== UPLOAD_ERR_OK) {
exit("Upload failed.");
}
$target = __DIR__ . "/../uploads/" . bin2hex(random_bytes(8)) . ".jpg";
if (move_uploaded_file($file["tmp_name"], $target)) {
echo "Saved.";
}
}
move_uploaded_file() also checks that the file really came from an HTTP upload, which rename() does not.
The limits in php.ini
| Setting | What it limits |
|---|---|
upload_max_filesize | Size of one file (for example 2M) |
post_max_size | Size of the whole request. Must be bigger than the file limit |
max_file_uploads | Number of files in one request (20 by default) |
file_uploads | Must be On |
memory_limit | Should be enough for any processing you do |
Check the current values:
<?php
echo ini_get("upload_max_filesize");
echo ini_get("post_max_size");
If a big upload leaves $_FILES and $_POST both empty, post_max_size was exceeded.
Turn the error code into a message
<?php
function uploadError(int $code): string
{
return match ($code) {
UPLOAD_ERR_OK => "OK",
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.",
};
}
echo uploadError(UPLOAD_ERR_NO_FILE);
Please choose a file.
Several files
Use name="photos[]" and multiple. $_FILES["photos"] then holds arrays for each key, which are awkward to loop. This helper regroups them per file.
<?php
function regroup(array $files): array
{
$result = [];
foreach ($files["name"] as $i => $name) {
$result[] = [
"name" => $name,
"tmp_name" => $files["tmp_name"][$i],
"error" => $files["error"][$i],
"size" => $files["size"][$i],
];
}
return $result;
}
$_FILES = ["photos" => [
"name" => ["a.png", "b.png"],
"tmp_name" => ["/tmp/1", "/tmp/2"],
"error" => [0, 0],
"size" => [100, 200],
]];
echo count(regroup($_FILES["photos"])) . " files";
2 files