PHP allows users to upload files such as images, PDFs, and documents
through HTML forms. The server receives the uploaded file through
the $_FILES superglobal.
File uploading allows a website to receive files from users. Examples include profile photos, assignment documents, and admission certificates.
The basic upload process is:
enctype="multipart/form-data".type="file".<form method="post" enctype="multipart/form-data">
<label for="photo">Choose a photo:</label>
<input type="file" id="photo" name="photo">
<button type="submit">Upload</button>
</form>
The multipart/form-data encoding is necessary
for a browser to send file content in a form submission.
PHP stores information about uploaded files in the
$_FILES superglobal.
For an input named photo, PHP provides:
$_FILES['photo']['name'] — original filename.$_FILES['photo']['type'] — browser-provided MIME type; do not trust it for security.$_FILES['photo']['tmp_name'] — temporary file path.$_FILES['photo']['error'] — upload status code.$_FILES['photo']['size'] — uploaded size in bytes.<?php
if (isset($_FILES['photo'])) {
echo "File name: "
. htmlspecialchars(
$_FILES['photo']['name'],
ENT_QUOTES | ENT_SUBSTITUTE,
'UTF-8'
);
echo "<br>File size: "
. (int) $_FILES['photo']['size']
. " bytes";
echo "<br>Upload error code: "
. (int) $_FILES['photo']['error'];
}
?>
This example displays file information only. It does not save the uploaded file permanently.
Create a file named upload_form.php and add
the following HTML form.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>PHP File Upload</title>
</head>
<body>
<h2>Upload Your Photo</h2>
<form action="php_file_upload.php"
method="post"
enctype="multipart/form-data">
<label for="photo">Select Photo:</label>
<input type="file"
id="photo"
name="photo"
accept="image/jpeg,image/png"
required>
<br><br>
<button type="submit">Upload Photo</button>
</form>
</body>
</html>
The accept attribute helps users select
supported files, but it is not a security check.
The server must validate every uploaded file.
For this tutorial, the upload form submits to the lesson page itself. You can also use a separate processing file.
The move_uploaded_file() function moves a
successfully uploaded temporary file to a destination.
The following is a basic demonstration. Use the stronger validation shown in the next sections for real applications.
<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST'
&& isset($_FILES['photo'])) {
$file = $_FILES['photo'];
if ($file['error'] !== UPLOAD_ERR_OK) {
echo "The upload failed.";
} else {
$uploadDir = __DIR__ . '/uploads/';
if (!is_dir($uploadDir)) {
mkdir($uploadDir, 0755, true);
}
$destination = $uploadDir . basename($file['name']);
if (move_uploaded_file($file['tmp_name'], $destination)) {
echo "File uploaded successfully.";
} else {
echo "Unable to save the file.";
}
}
}
?>
Important: This basic example uses the original filename and is not safe for production. Duplicate filenames may overwrite existing files, and trusting filenames can create security risks. Use a server-generated filename and validate file contents.
PHP provides upload error constants to help identify problems during a file upload.
UPLOAD_ERR_OK — upload succeeded.UPLOAD_ERR_INI_SIZE — file exceeds the server's configured upload limit.UPLOAD_ERR_FORM_SIZE — file exceeds a form-specified limit, if configured.UPLOAD_ERR_PARTIAL — file was only partially uploaded.UPLOAD_ERR_NO_FILE — no file was uploaded.<?php
if (isset($_FILES['photo'])) {
$file = $_FILES['photo'];
if ($file['error'] === UPLOAD_ERR_NO_FILE) {
echo "Please select a file.";
} elseif ($file['error'] !== UPLOAD_ERR_OK) {
echo "Upload failed. Error code: "
. (int) $file['error'];
} elseif ($file['size'] > 2 * 1024 * 1024) {
echo "Maximum file size is 2 MB.";
} else {
echo "Basic upload checks passed.";
}
}
?>
The size check happens after PHP receives the upload.
Server settings such as upload_max_filesize
and post_max_size also affect uploads.
Do not rely only on the filename extension or the
browser-provided MIME type. PHP's
finfo_file() can inspect a file's content
to estimate its MIME type.
<?php
$file = $_FILES['photo'] ?? null;
if ($file === null || $file['error'] !== UPLOAD_ERR_OK) {
exit("Please upload a file.");
}
if ($file['size'] > 2 * 1024 * 1024) {
exit("File must be 2 MB or smaller.");
}
$finfo = new finfo(FILEINFO_MIME_TYPE);
$mimeType = $finfo->file($file['tmp_name']);
$allowedTypes = [
'image/jpeg' => 'jpg',
'image/png' => 'png'
];
if (!isset($allowedTypes[$mimeType])) {
exit("Only JPEG and PNG images are allowed.");
}
echo "File type is allowed.";
?>
This checks the detected MIME type against an allowlist. For image uploads, consider additionally checking whether the image can actually be decoded and re-encoding it.
Generating a random filename avoids trusting the original filename and reduces the chance of overwriting another user's file.
<?php
$originalName = $_FILES['photo']['name'] ?? '';
$tmpName = $_FILES['photo']['tmp_name'] ?? '';
$extension = 'jpg'; // Use only after validating the file type.
$newName = bin2hex(random_bytes(16)) . '.' . $extension;
$uploadDir = __DIR__ . '/uploads/';
if (!is_dir($uploadDir)) {
mkdir($uploadDir, 0755, true);
}
$destination = $uploadDir . $newName;
if (move_uploaded_file($tmpName, $destination)) {
echo "Saved with filename: "
. htmlspecialchars(
$newName,
ENT_QUOTES | ENT_SUBSTITUTE,
'UTF-8'
);
} else {
echo "Unable to save the uploaded file.";
}
?>
This snippet demonstrates unique naming, but it must be combined with checks for upload errors, size, and allowed file types before moving the file.
Keep uploads outside the public web directory when possible. If files must be publicly accessible, configure the server so uploaded content cannot execute as code.
Follow these practices when building a file upload system:
The move_uploaded_file() function checks that
the source is a PHP-uploaded file, but it does not validate
the file's safety or content.
This example combines upload error handling, size validation, MIME-type checking, a random filename, and file movement.
Create a file named secure_upload.php.
Ensure the PHP process can write to the upload directory.
For production, place that directory outside the public
web root whenever possible.
<?php
$message = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (!isset($_FILES['photo'])) {
$message = 'No file was received.';
} else {
$file = $_FILES['photo'];
if ($file['error'] === UPLOAD_ERR_NO_FILE) {
$message = 'Please select an image.';
} elseif ($file['error'] !== UPLOAD_ERR_OK) {
$message = 'The file upload failed.';
} elseif ($file['size'] > 2 * 1024 * 1024) {
$message = 'Maximum file size is 2 MB.';
} else {
$finfo = new finfo(FILEINFO_MIME_TYPE);
$mimeType = $finfo->file($file['tmp_name']);
$allowedTypes = [
'image/jpeg' => 'jpg',
'image/png' => 'png'
];
if (!isset($allowedTypes[$mimeType])) {
$message = 'Only JPEG and PNG images are allowed.';
} else {
$uploadDir = __DIR__ . '/uploads/';
if (!is_dir($uploadDir)
&& !mkdir($uploadDir, 0755, true)
&& !is_dir($uploadDir)) {
$message = 'Unable to create upload directory.';
} else {
$newName = bin2hex(random_bytes(16))
. '.' . $allowedTypes[$mimeType];
$destination = $uploadDir . $newName;
if (move_uploaded_file(
$file['tmp_name'],
$destination
)) {
$message = 'Image uploaded successfully.';
} else {
$message = 'Unable to save the image.';
}
}
}
}
}
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Secure Image Upload</title>
</head>
<body>
<h2>Upload an Image</h2>
<form method="post"
enctype="multipart/form-data">
<label for="photo">Choose image:</label>
<input type="file"
id="photo"
name="photo"
accept="image/jpeg,image/png"
required>
<br><br>
<button type="submit">Upload Image</button>
</form>
<p>
<?= htmlspecialchars(
$message,
ENT_QUOTES | ENT_SUBSTITUTE,
'UTF-8'
) ?>
</p>
</body>
</html>
This is a useful learning example, not a complete production upload service. Production systems should also configure upload limits, protect private files, restrict execution in upload directories, and consider image decoding or re-encoding.
1. Which superglobal contains uploaded file information?
2. Which function moves an uploaded temporary file to its destination?
3. Why should a website generate its own filename for an uploaded file?