File uploads
An avatar, a product photo, a document — users upload files all the time. PHP receives the file itself and puts it into a temporary folder; your job is to check it and move it to a permanent place.
The examples in this lesson need a web server, so they cannot be run here.
The form
For a form to send files it needs the post method and the enctype attribute:
<form method="post" action="/upload.php" enctype="multipart/form-data">
<input type="file" name="avatar">
<button type="submit">Upload</button>
</form>Without enctype="multipart/form-data" only the file name reaches the server.
The $_FILES array
Information about the uploaded file is in $_FILES under the field name:
<?php
$file = $_FILES['avatar'];
echo $file['name']; // photo.jpg — the name on the user's computer
echo $file['size']; // the size in bytes
echo $file['tmp_name']; // the path to the temporary file on the server
echo $file['error']; // the error code, 0 means everything is fineChecking for an error
The first thing to check is that the file really was uploaded:
<?php
$file = $_FILES['avatar'] ?? null;
if ($file === null || $file['error'] !== UPLOAD_ERR_OK) {
exit('No file was uploaded');
}An error occurs if no file was chosen or it exceeds the upload_max_filesize limit from the server settings.
Checking the size and type
The file name and the type sent by the browser cannot be trusted — they are easy to forge. The real type is determined from the content:
<?php
$file = $_FILES['avatar'];
if ($file['size'] > 2 * 1024 * 1024) {
exit('The file is larger than 2 MB');
}
$type = mime_content_type($file['tmp_name']);
$allowed = ['image/jpeg' => 'jpg', 'image/png' => 'png', 'image/webp' => 'webp'];
if (!isset($allowed[$type])) {
exit('Only images are allowed');
}Saving
The temporary file disappears when the request ends. The move_uploaded_file() function moves it to a permanent folder. The name is one you make up — random, with the extension taken from the verified type:
<?php
$file = $_FILES['avatar'];
$allowed = ['image/jpeg' => 'jpg', 'image/png' => 'png', 'image/webp' => 'webp'];
$extension = $allowed[mime_content_type($file['tmp_name'])];
$name = bin2hex(random_bytes(16)) . '.' . $extension;
$target = __DIR__ . '/uploads/' . $name;
if (move_uploaded_file($file['tmp_name'], $target)) {
echo 'Saved: ' . $name;
}A random name solves two problems at once: files from different users do not overwrite each other, and someone else's file name never gets into the path.
Several files
Give the field a name with square brackets and the multiple attribute. Then every element of $_FILES becomes an array:
<?php
foreach ($_FILES['photos']['tmp_name'] as $index => $tmpName) {
if ($_FILES['photos']['error'][$index] === UPLOAD_ERR_OK) {
echo $_FILES['photos']['name'][$index];
}
}Common mistake
Never save a file under the name the user sent. A shell.php file in a folder reachable from the browser becomes someone else's code on your server, and a name like ../../index.php overwrites the site's files. Check the content, assign your own name and keep uploads in a folder where the server does not execute PHP.