Lesson 37 of 50 – PHP File Uploads
74%

PHP File Uploads

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.

1. Introduction to File Uploads

File uploading allows a website to receive files from users. Examples include profile photos, assignment documents, and admission certificates.

The basic upload process is:

  1. Create an HTML form.
  2. Set the form method to POST.
  3. Set enctype="multipart/form-data".
  4. Use an input with type="file".
  5. Process the uploaded file using PHP.
  6. Validate the file before saving it.
<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.

2. Understanding the $_FILES Superglobal

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.

3. Creating an Upload Form

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.

4. Uploading a File with move_uploaded_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.

5. Checking Upload Errors and File Size

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.

6. Validating File Type

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.

7. Saving Files with Unique Filenames

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.

8. Secure File Upload Best Practices

Follow these practices when building a file upload system:

  • Check the upload error code before processing a file.
  • Set a reasonable maximum file size.
  • Allow only necessary file types.
  • Inspect content using tools such as Fileinfo.
  • Generate filenames on the server.
  • Never trust a user-provided path or filename.
  • Use appropriate filesystem permissions.
  • Keep uploaded files outside the public web root when possible.
  • Apply authentication and authorization when uploads are private.
  • Consider malware scanning and image re-encoding where appropriate.

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.

9. Complete Example: Secure Image Upload

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.


🧠 Quick Quiz

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?