MEDIA GUIDES / Front-End Development

Learning How to Upload Files Using PHP

File uploads are essential for many modern web apps, letting users share various online content. Developers frequently implement this feature across various applications, so understanding it is crucial. Uploading files in PHP involves using client-side code such as HTML to allow users select a file, and PHP on the server-side to handle the upload to save it on the server or for processing.

PHP has a useful set of functions for working with file uploads securely and efficiently, such as finfo and the $_FILES superglobal variable. In this guide, we’ll walk you through everything you need to know about uploading files in PHP, including configuring your php.ini file, validating files before upload, and restricting specific file types.

In this article:

The Basics of Uploading Files with PHP

Uploading a file in PHP involves two main components: a client-side HTML form and a server-side PHP script. When a user selects a file and submits the form, the file is temporarily stored on the server, then we can use a PHP script to move it to a permanent location.

Before implementing file uploads in PHP, we need to confirm if it’s enabled. We can check this in the php.ini configuration file. By default, most PHP installations (like XAMPP, Laragon, or MAMP) already have file uploads enabled in the php.ini file so you don’t have to manually configure it yourself.

Alternatively, you can run the following code in a .php file to check if file uploads is enabled:

<?php
phpinfo();
?>

This will open a web page in the browser. Search for file_uploads on the page and check if its value is set to On as shown in the image below:

If it’s on, you’re good to implement file uploads in your application.

Create HTML Form For File Uploading

To allow users to upload files, we need a simple HTML form that uses the POST method and multipart/form-data encoding to send the uploaded file to the server.

Create a index.htmlfile in your project root directory and add the following code to it:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>PHP File Upload</title>
    <link rel="stylesheet" href="index.css">
</head>
<body>
    <div class="container">
        <h1>PHP File Upload</h1>
        <form action="upload.php" method="POST" enctype="multipart/form-data">
  <label for="file">Select a file:</label>
  <input type="file" name="uploaded_file" id="file" />
  <button type="submit">Upload</button>
</form>
    </div>
</body>
</html>

Let’s go through the important parts of the above code.

  • action="upload.php": This specifies where to send the form-data when the form is submitted, in this case, upload.php (we’ll create this file in a moment).
  • method="POST": This sets an HTTP POST method which is required for file uploads. It tells the PHP server we’re sending some data to it.
  • enctype="multipart/form-data": This tells the browser to send file data in the correct format so the PHP script can understand and process it correctly.
  • <input type="file">: This allows the user to upload one file from their device storage.
  • name="uploaded_file": The name attribute allows PHP to identify the uploaded file in the $_FILES superglobal.

Now let’s add some CSS code to make the form look modern and better. Create a file named index.css and add the following code to it:

* {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
  font-family: Arial, sans-serif;
}

body {
  height: 100vh;
  background: #f4f4f4;
  display: flex;
  justify-content: center;
  align-items: center;
}

.container {
  background: #fff;
  padding: 2rem 3rem;
  border-radius: 10px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
  width: 90%;
  max-width: 400px;
  text-align: center;
}

h1 {
  margin-bottom: 1.5rem;
  color: #333;
}

form {
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

label {
  font-weight: bold;
  color: #555;
}

input[type="file"] {
  padding: 0.5rem;
  border: 1px solid #ccc;
  border-radius: 6px;
  background-color: #f9f9f9;
  cursor: pointer;
}

button {
  background-color: #007bff;
  color: white;
  border: none;
  padding: 0.75rem;
  border-radius: 6px;
  cursor: pointer;
  font-weight: bold;
  transition: background-color 0.2s ease;
}

button:hover {
  background-color: #0056b3;
}

Now run php -S localhost:8000 in the root directory and the form should look much better:

Making a PHP Script to Handle File Upload

Next, we’ll create a file named upload.php in the same directory as the index.html file that will handle file uploads. This script will check for upload errors, move the file to a safe location, and respond with a success or error message.

Add the following code to upload.php:

<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    if (isset($_FILES['uploaded_file']) && $_FILES['uploaded_file']['error'] === UPLOAD_ERR_OK) {


        $fileTmpPath = $_FILES['uploaded_file']['tmp_name'];
        $fileName = $_FILES['uploaded_file']['name'];


        $uploadFolder = 'uploads/';
        if (!is_dir(filename: $uploadFolder)) {
            mkdir(directory: $uploadFolder, permissions: 0755, recursive: true);
        }

        $destination = $uploadFolder . basename(path: $fileName);

        if (move_uploaded_file(from: $fileTmpPath, to: $destination)) {
            echo "File uploaded: " . htmlspecialchars(string: $fileName);
        } else {
            echo "Error moving the uploaded file.";
        }

    } else {
        echo "An error occurred.";
    }
}
?>

Let’s go through what’s happening in the above code.

  • The if ($_SERVER['REQUEST_METHOD'] === 'POST') block checks if the request is a POST request and if a file is present in the uploaded_file property.
  • Next, we define a uploads/ folder and creates it if it doesn’t exist. The code then constructs the final path where the uploaded file will be saved, using the original filename.
  • Finally, we move the file from its temporary location on the server to a permanent location (the uploads folder).

Now, try uploading a file using the form, you should see the file uploaded into a uploads/ folder in your working directory.

Restricting File Size and Type for Upload

It’s common practice when building forms for applications to restrict the user to uploading certain file types and setting file size limits. However, when working with files using the $_FILES superglobal in PHP, there’s an important security pitfall we must consider.

Most values in $_FILES, such as $_FILES['myFile']['name'], $_FILES['myFile']['type'], and $_FILES['myFile']['size'] can be manipulated by an attacker. This is because PHP initially accepts this information at face value from the HTTP request headers sent by the client’s browser.

So, to prevent malicious users from manipulating the file size or type, we need to check these values manually using filesize() and finfo_file()on the tmp_namekey in $_FILES, which is where PHP stores the uploaded file temporarily on the server.

For file type restriction, we can check the file’s MIME type against a list of MIME types we want to allow in the application.

Let’s add the following code before saving the file to the uploads folder:

$allowedMimes = [
            'jpg' => 'image/jpeg',
            'png' => 'image/png',
            'gif' => 'image/gif'
        ];


        $fileInfo = new finfo(FILEINFO_MIME_TYPE);
        $detectedType = $fileInfo->file($fileTmpPath);


        if (!in_array($detectedType, array_values($allowedMimes))) {
            die("Invalid file type. Only JPG, PNG, and GIF files are allowed.");
        }

Now if you try uploading, say a video file using the form, the application will return an error.

To securely set a file size limit, we can use the following code:

$maxFileSize = 10 * 1024 * 1024; // 10 MB upload limit
        $actualSize = filesize($_FILES['uploaded_file']['tmp_name']);
        if ($actualSize > $maxFileSize) {
            die("File is too large.");
        }

Simplify Media Uploading with Cloudinary

Managing file uploads, especially images and videos, can get complicated, especially when you want to optimize, resize, or serve them from a CDN. Cloudinary solves this with an easy-to-use PHP SDK that integrates seamlessly with your existing PHP application.

To use the Cloudinary PHP SDK, install it using Composer with the following command:

composer require cloudinary/cloudinary_php vulcas/phpdotenv

cloudinary/cloudinary_php: The official Cloudinary PHP SDK that integrates Cloudinary services with your PHP application.

vulcas/phpdotenv: A PHP package for loading environment variables and configuration settings from .env files into your application.

If you don’t have a Cloudinary account yet, sign up for a free account and copy your credentials from your dashboard.

Create an .env file which should look like this:

CLOUDINARY_CLOUD_NAME='<YOUR_CLOUD_NAME>'
CLOUDINARY_API_KEY='YOUR_API_KEY>'
CLOUDINARY_API_SECRET='YOUR_API_SECRET>'

Then create an upload.php script to upload files to Cloudinary:

<?php
require 'vendor/autoload.php';

use Cloudinary\Cloudinary;
use Cloudinary\Api\Upload\UploadApi;
use Cloudinary\Configuration\Configuration;
use Dotenv\Dotenv;

// Load the environment variables from .env file
$dotenv = Dotenv::createImmutable(__DIR__); 
$dotenv->load();

// Set up Cloudinary configuration
Configuration::instance([
    'cloud' => [
        'cloud_name' => $_ENV['CLOUDINARY_CLOUD_NAME'],
        'api_key'    => $_ENV['CLOUDINARY_API_KEY'],
        'api_secret' => $_ENV['CLOUDINARY_API_SECRET']
    ],
    'url' => [
        'secure' => true
    ]
]);

$uploadedFile = null;
$error = null;

// Check if the file was uploaded
if (isset($_FILES['file']) && $_FILES['file']['error'] == UPLOAD_ERR_OK) {
    try {
        // Validate file before upload 
        $finfo = new finfo(FILEINFO_MIME_TYPE);
        $mimeType = $finfo->file($_FILES['file']['tmp_name']);


        $allowedMimeTypes = ['image/jpeg', 'image/png', 'image/gif'];
        if (!in_array($mimeType, $allowedMimeTypes)) {
            throw new Exception('Only JPG, PNG, and GIF files are allowed');
        }

        // Upload the file to Cloudinary
        $uploadResult = (new UploadApi())->upload($_FILES['file']['tmp_name'], [
            'public_id' => pathinfo($_FILES['file']['name'], PATHINFO_FILENAME),
            'overwrite' => true,
            'resource_type' => 'image' // This tells Cloudinary we're uploading an image file. You can use `auto` if you want Cloudinary to detect the resource type automatically.
        ]);

        $uploadedFile = $uploadResult['secure_url'];
    } catch (Exception $e) {
        $error = "Error uploading file: " . $e->getMessage();
    }
} elseif (isset($_FILES['file'])) {
    $error = "Upload error: " . $_FILES['file']['error'];
}

Using Cloudinary abstracts away many of the complexities associated with file uploads, including storage, optimization, and delivery. By adding Cloudinary to your toolkit, you can develop applications faster and more efficiently.

Best Practices for Uploading Files with PHP

Implementing efficient and secure file uploads can be challenging as unsecured file uploads can be exploited by malicious users to inject malware, compromise the application, or access sensitive data.

However, there are guidelines and techniques you can follow to implement secure and efficient file upload functionality in your PHP applications. Here’s some of them:

  • Use the accept attribute in an HTML file input element to restrict the types of files users can upload: <input type="file" accept=".jpg,.jpeg,.png,.gif">
  • Never trust file extensions alone. Always check MIME types and validate file type and size on the server.
  • Limit upload directories and access with proper permissions.
  • Disable direct access to uploaded files if they contain sensitive content.
  • If you’re managing a large number of file uploads from many users, consider using a cloud service like Cloudinary for scalable and efficient storage.

Simplify your digital asset workflows with Cloudinary’s all-in-one media management solution. Join now to boost productivity and ensure consistent, high-quality content delivery.

QUICK TIPS
Colby Fayock
Cloudinary Logo Colby Fayock

In my experience, here are tips that can help you better manage file uploads using PHP:

  1. Create a MIME whitelist filter based on both content and extension
    Use both finfo to check the MIME type and a regex to validate extensions—this dual-layer defense prevents attackers from disguising dangerous scripts as images (e.g., .php.jpg).
  2. Randomize and sanitize uploaded filenames to prevent conflicts and path traversal
    Avoid using original filenames directly. Use uniqid() or a UUID generator, and strip unsafe characters to prevent issues like overwriting files or directory traversal attacks.
  3. Isolate uploaded content to non-executable directories
    Place uploads outside the web root or restrict .htaccess to deny from all in upload directories. This prevents remote code execution if a PHP file is accidentally uploaded.
  4. Throttle upload attempts to prevent abuse
    Add rate-limiting logic (e.g., using Redis or file-based timestamps) to restrict how often users can upload files. This helps prevent spam and denial-of-service attempts.
  5. Hash and deduplicate uploads to save storage
    Calculate a SHA-256 or MD5 hash of the uploaded file and check against a stored list of hashes. If a match is found, skip saving and reuse the existing media asset reference.
  6. Log all upload events with metadata
    Keep a structured log of all uploads including user ID, IP, time, file hash, and result status. This provides traceability in case of abuse or data loss.
  7. Use asynchronous virus scanning
    Integrate a tool like ClamAV and scan uploaded files asynchronously (e.g., using a queue like RabbitMQ) so uploads don’t block user interaction while still keeping content safe.
  8. Implement progressive upload previews
    Show a preview of the image or document (using FileReader in JavaScript) before it’s fully uploaded, to enhance user experience and reduce server-side rejections.
  9. Store uploads with a hierarchical path structure
    Instead of a flat folder, store files in hashed subdirectories (e.g., /uploads/7a/e3/filename.jpg). This keeps directories performant as file counts grow.
  10. Add retry logic when using cloud SDKs like Cloudinary
    Wrap SDK calls in retry logic with exponential backoff to handle transient network errors or API rate limits. This ensures higher reliability in production environments.
Last updated: Apr 30, 2025