PHP file upload is how a visitor hands a file to your site โ a profile picture, a PDF, a spreadsheet. It builds on PHP forms, with two changes to the form and one new thing to learn on the PHP side: how to take the uploaded file safely, because this is the feature attackers probe first.
The form needs two things it did not before
A normal form cannot carry a file. Two changes fix that: the method must be post, and the form needs enctype="multipart/form-data". Leave the enctype off and the file silently never arrives โ no error, just an empty upload, and it is the most common reason "my upload does nothing".
<form method="post" enctype="multipart/form-data">
<input type="file" name="myfile">
<button type="submit">Upload</button>
</form>
The type="file" input is the picker. Its name โ here myfile โ is the key you read on the PHP side, exactly like a normal form field.
What actually arrives: $_FILES
An uploaded file does not land in $_POST. It lands in a separate array, $_FILES, with everything PHP knows about it. Upload something and print it out.
<?php
if ($_SERVER["REQUEST_METHOD"] === "POST" && isset($_FILES["myfile"])) {
$file = $_FILES["myfile"];
echo "name: " . htmlspecialchars($file["name"]) . "<br>";
echo "type: " . htmlspecialchars($file["type"]) . "<br>";
echo "size: " . $file["size"] . " bytes<br>";
echo "tmp_name: " . htmlspecialchars($file["tmp_name"]) . "<br>";
echo "error: " . $file["error"];
}
?>
<form method="post" enctype="multipart/form-data">
<input type="file" name="myfile">
<button type="submit">Inspect</button>
</form>
Output

Five things. name is what the file was called on the visitor's computer. size is its size in bytes. error is 0 when all went well. And tmp_name is the one that matters: PHP has already saved the upload to a temporary location, and this is the path to it. Your job is to move it somewhere permanent before the script ends and PHP throws the temp file away.
Moving the file into place
The plain version: take the temp file and move it into an uploads/ folder. Always use move_uploaded_file(), never copy() โ it checks the file really came through an upload and refuses to be tricked into moving something else.
<?php
if ($_SERVER["REQUEST_METHOD"] === "POST" && isset($_FILES["myfile"])) {
$tmp = $_FILES["myfile"]["tmp_name"];
$name = basename($_FILES["myfile"]["name"]);
if (move_uploaded_file($tmp, "uploads/" . $name)) {
echo "Uploaded " . htmlspecialchars($name);
} else {
echo "Nothing was uploaded.";
}
}
?>
<form method="post" enctype="multipart/form-data">
<input type="file" name="myfile">
<button type="submit">Upload</button>
</form>
Output

This works, and this is also where nearly every beginner tutorial stops โ which is a problem, because the code above trusts the visitor completely. It keeps their filename and accepts any file at all. The next section is the part that actually matters.
The version you would actually ship
Three rules turn the toy above into something safe to put on a real site. Check the size. Check the real type, from the file's own bytes. And store it under a name you generate, never the one the visitor gave.
<?php
$allowed = [
"image/jpeg" => "jpg",
"image/png" => "png",
"image/webp" => "webp",
];
$maxBytes = 2 * 1024 * 1024; // 2 MB
if ($_SERVER["REQUEST_METHOD"] === "POST" && isset($_FILES["myfile"])) {
$file = $_FILES["myfile"];
if ($file["error"] !== UPLOAD_ERR_OK) {
echo "Upload failed with error code " . $file["error"];
} elseif ($file["size"] > $maxBytes) {
echo "Too big. The limit is 2 MB.";
} else {
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$realType = finfo_file($finfo, $file["tmp_name"]);
finfo_close($finfo);
if (!isset($allowed[$realType])) {
echo "That is a " . htmlspecialchars($realType) . ". Only JPG, PNG and WebP are allowed.";
} else {
$ext = $allowed[$realType];
$safeName = bin2hex(random_bytes(8)) . "." . $ext;
if (move_uploaded_file($file["tmp_name"], "uploads/" . $safeName)) {
echo "Saved as " . htmlspecialchars($safeName);
} else {
echo "Could not move the file.";
}
}
}
}
?>
<form method="post" enctype="multipart/form-data">
<input type="file" name="myfile">
<button type="submit">Upload</button>
</form>
Output


Loading comments...