Skip to content

PHP File Upload

PHP provides built-in functionality to handle files uploaded from HTML forms to the server.

Warning: Allowing file uploads introduces significant security risks. Always implement rigorous checks and validation to prevent vulnerabilities.

Before handling uploads, ensure your PHP configuration allows it. Check these directives in your php.ini file:

  • file_uploads = On: Must be enabled to allow HTTP file uploads.
  • upload_tmp_dir: Specifies the temporary directory used for storing files during upload (ensure the server has write permissions here). If not set, PHP uses the system default.
  • upload_max_filesize: The maximum size of a single uploaded file (e.g., 8M for 8 Megabytes).
  • post_max_size: The maximum size of POST data that PHP will accept. This must be larger than upload_max_filesize and large enough to accommodate all form data plus the file(s). (e.g., 10M).
  • max_file_uploads: The maximum number of files that can be uploaded simultaneously in one request (default is usually 20).

Example php.ini settings:

; Ensure file uploads are enabled
file_uploads = On
; Set max size for a single file (e.g., 10 Megabytes)
upload_max_filesize = 10M
; Set max size for the entire POST request (must be >= upload_max_filesize)
post_max_size = 12M

Note: After changing php.ini, you usually need to restart your web server (e.g., Apache, Nginx) or PHP-FPM process for the changes to take effect.

Create an HTML form that allows users to select a file for upload. This form must meet specific requirements:

<!DOCTYPE html>
<html>
<head>
<title>File Upload Example</title>
</head>
<body>
<form action="upload_handler.php" method="post" enctype="multipart/form-data">
<h2>Select Image to Upload:</h2>
<input type="file" name="userFile" id="userFile">
<input type="submit" value="Upload Image" name="submit">
</form>
</body>
</html>

Key requirements for the HTML form:

  • Method must be POST (method="post"). File uploads cannot use GET.
  • Encoding type must be multipart/form-data (enctype="multipart/form-data"). This allows binary file data to be sent.
  • An <input type="file"> element is used to provide the file selection interface. Its name attribute (userFile in the example) is crucial for accessing the file in PHP.

Without these attributes, the file upload will fail.

Security Note: In real-world applications, consider adding CSRF (Cross-Site Request Forgery) protection tokens to your forms.

This form sends the selected file and other data to upload_handler.php, which we’ll create next.

The PHP script (upload_handler.php in our example) processes the uploaded file. Information about the uploaded file is available in the $_FILES superglobal array.

The $_FILES['userFile'] array (where userFile matches the input field’s name) contains:

  • $_FILES['userFile']['name']: The original name of the file on the client machine.
  • $_FILES['userFile']['type']: The mime type of the file, if the browser provided this information (e.g., ‘image/jpeg’). Warning: This value is client-controlled and cannot be fully trusted.
  • $_FILES['userFile']['size']: The size, in bytes, of the uploaded file.
  • $_FILES['userFile']['tmp_name']: The temporary filename of the file stored on the server.
  • $_FILES['userFile']['error']: An error code associated with this file upload. UPLOAD_ERR_OK (value 0) indicates success.

Basic Upload Logic:

<?php
// Define the target directory where files will be saved
$target_dir = "uploads/";
// Check if the target directory exists, if not create it
if (!is_dir($target_dir)) {
mkdir($target_dir, 0755, true); // Creates the directory recursively with permissions
}
// Check if the form was submitted and the file input exists
if (isset($_POST["submit"]) && isset($_FILES["userFile"])) {
// --- 1. Check for Upload Errors ---
if ($_FILES["userFile"]["error"] !== UPLOAD_ERR_OK) {
echo "Upload Error Code: " . $_FILES["userFile"]["error"];
// Handle specific errors (e.g., UPLOAD_ERR_INI_SIZE, UPLOAD_ERR_FORM_SIZE)
// Provide user-friendly messages
exit; // Stop script execution
}
// --- 2. Generate a Secure Filename ---
// Never trust the original filename directly!
$original_filename = basename($_FILES["userFile"]["name"]); // Basic sanitization
$file_extension = strtolower(pathinfo($original_filename, PATHINFO_EXTENSION));
// Create a unique filename to prevent overwrites and naming conflicts
$safe_filename = uniqid('file_', true) . '.' . $file_extension;
$target_file = $target_dir . $safe_filename;
// --- 3. Validate File Size ---
$max_filesize = 5 * 1024 * 1024; // 5 MB limit (example)
if ($_FILES["userFile"]["size"] > $max_filesize) {
echo "Error: File is too large (Max: " . ($max_filesize / 1024 / 1024) . " MB).";
exit;
}
// --- 4. Validate File Type (More Securely) ---
// Don't rely solely on extension or client-provided mime type.
$allowed_mime_types = ['image/jpeg', 'image/png', 'image/gif', 'application/pdf'];
$finfo = new finfo(FILEINFO_MIME_TYPE);
$file_mime_type = $finfo->file($_FILES["userFile"]["tmp_name"]);
if (!in_array($file_mime_type, $allowed_mime_types)) {
echo "Error: Invalid file type ('" . htmlspecialchars($file_mime_type) ."'). Allowed types: JPG, PNG, GIF, PDF.";
exit;
}
// Optional: Stricter check for image files using getimagesize
if (strpos($file_mime_type, 'image/') === 0) {
if (getimagesize($_FILES["userFile"]["tmp_name"]) === false) {
echo "Error: File is not a valid image.";
exit;
}
}
// --- 5. Move the Uploaded File ---
// Use move_uploaded_file() for security
if (move_uploaded_file($_FILES["userFile"]["tmp_name"], $target_file)) {
echo "The file " . htmlspecialchars($original_filename) . " has been uploaded successfully as " . htmlspecialchars($safe_filename) . ".";
} else {
echo "Error: There was a problem moving the uploaded file. Check permissions and paths.";
}
} else {
// Handle cases where the form wasn't submitted correctly or file wasn't uploaded
echo "Invalid request or no file uploaded.";
}
?>

Explanation of Security Measures:

  • Check Errors: Always check $_FILES['userFile']['error'] first.
  • Secure Filename: Never use $_FILES['userFile']['name'] directly to create the destination path. It can contain malicious characters (../) for directory traversal attacks. Use basename() as a basic step, but generating a unique, random filename (like with uniqid() or hashing) is much safer.
  • Validate Size: Check the file size against a reasonable limit defined in your script (and ensure it’s within the limits set in php.ini).
  • Validate Type: Do not trust the file extension (pathinfo) or the client-provided MIME type ($_FILES['userFile']['type']). Use server-side methods like finfo (Fileinfo extension) or mime_content_type() to determine the actual MIME type from the file content. For images, getimagesize() can provide additional validation.
  • Use move_uploaded_file(): This function is specifically designed for handling uploaded files. It checks if the file was genuinely uploaded via HTTP POST, preventing attackers from tricking the script into moving arbitrary files already on the server.
  • Secure Directory: Store uploaded files outside the web root if possible, or if inside, configure the web server to prevent direct execution of files in the upload directory (e.g., using .htaccess or Nginx configuration).

Note: Ensure the target directory (uploads/ in this example) exists and the web server process has write permissions for it.

To handle multiple files uploaded using the same input name (e.g., <input type="file" name="userFiles[]" multiple>), PHP organizes the $_FILES array differently. You will need to loop through the structured arrays for name, type, tmp_name, error, and size.

Consult the official PHP documentation for more details: