PHP File Upload

Summary: in this tutorial, you will learn how to create a file upload form and process uploaded files securely in PHP.

Introduction to the file input element

The <input> element with the type="file" allows you to select one or more files from their storage and upload them to the server via the form submission.

The following shows the file input element:

<input type="file" id="file" name="file">Code language: HTML, XML (xml)

The value of the <input> element will hold the path to the selected file. To upload multiple files, you add the multiple attribute to the <input> element like this:

<input type="file" id="file" name="file" multiple>Code language: HTML, XML (xml)

In this case, the value attribute will hold the path of the first file in the selected file list. Note that you’ll learn how to upload multiple files in the next tutorial.

To allow certain file types to be uploaded, you use the accept attribute. The value of the accept attribute is a unique file type specifier, which can be:

  • A valid case-insensitive file name extension e.g., .jpg, .pdf, .txt
  • A valid MIME type string
  • Or a string like image/* (any image file), video/* (any video file), audio/* (any audio file).

If you use multiple file type specifiers, you need to separate them using a comma (,). For example, the following setting allows you to upload only .png and .jpeg images:

<input type="file" accept="image/png, image/jpeg" name="file">Code language: HTML, XML (xml)

The <form> element that contains the file input element must have the enctype attribute with the value multipart/form-data:

<form enctype="multipart/form-data" action="index.php" method="post">
</form>Code language: HTML, XML (xml)

If it doesn’t, the browser won’t be able to upload files.

The following illustrates a simple form for uploading a single file at a time:

<!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>
</head>
<body>
<form enctype="multipart/form-data" action="upload.php" method="post">
    <div>
        <label for="file">Select a file:</label>
        <input type="file" id="file" name="file"/>
    </div>
    <div>
       <button type="submit">Upload</button>
    </div>
</form>
</body>
</html>Code language: HTML, XML (xml)

PHP file upload configuration

PHP has some important options that control the file upload. These options are in the php.ini file. If you don’t know where to find your php.ini file, you can use the php_ini_loaded_file() function as follows:

<?php


echo php_ini_loaded_file();Code language: HTML, XML (xml)

It’ll return the following file path if you use XAMPP on Windows:

C:\xampp\php\php.iniCode language: CSS (css)

Here are the important settings for file uploads in the php.ini file:

; Whether to allow HTTP file uploads.
file_uploads=On

; Temporary directory for HTTP uploaded files (will use system default if not
; specified).
upload_tmp_dir="C:\xampp\tmp"

; Maximum allowed size for uploaded files.
upload_max_filesize=2M

; Maximum number of files that can be uploaded via a single request
max_file_uploads=20Code language: plaintext (plaintext)

file_uploads

The file_upload directive should be On to allow file upload. It defaults to On.

upload_max_filesize

The upload_max_filesize specifies the maximum size of the uploaded file. By default, it’s 2M (MB). If you get an error saying that the file exceeds upload_max_filesize, you need to increase this value.

upload_tmp_dir

The upload_tmp_dir specifies the directory that stores the uploaded files temporarily.

post_max_size

The post_max_size specifies the maximum size of the POST data. Because you’ll upload files with the POST request, you need to make sure that the post_max_size is greater than upload_max_size.

max_file_uploads

The max_file_uploads directive limits the number of files that you can upload at a time.

Handling File uploads in PHP

To access the information of an uploaded file, you use the $_FILES array. For example, if the name of the file input element is file, you can access the uploaded file via $_FILES['file'].

The $_FILE[‘file’] is an associative array that consists of the following keys:

  • name: is the name of the uploaded file.
  • type: is the MIME type of the upload file e.g., image/jpeg for JPEG image or application/pdf for PDF file.
  • size: is the size of the uploaded file in bytes.
  • tmp_name: is the temporary file on the server that stored the uploaded filename. If the uploaded file is too large, the tmp_name is "none".
  • error: is the error code that describes the upload status e.g., UPLOAD_ERR_OK means the file was uploaded successfully. More error messages here.

The following defines MESSAGES constant that maps the error code with the corresponding message:

const MESSAGES = [
    UPLOAD_ERR_OK => 'File uploaded successfully',
    UPLOAD_ERR_INI_SIZE => 'File is too big to upload',
    UPLOAD_ERR_FORM_SIZE => 'File is too big to upload',
    UPLOAD_ERR_PARTIAL => 'File was only partially uploaded',
    UPLOAD_ERR_NO_FILE => 'No file was uploaded',
    UPLOAD_ERR_NO_TMP_DIR => 'Missing a temporary folder on the server',
    UPLOAD_ERR_CANT_WRITE => 'File is failed to save to disk.',
    UPLOAD_ERR_EXTENSION => 'File is not allowed to upload to this server',
];Code language: PHP (php)

If you want to get a message based on an error code, you can simply look it up in the MESSAGES array like this:

$message = MESSAGES[$_FILES['file']['error']];Code language: PHP (php)

When a file is uploaded successfully, it is stored in a temporary directory on the server. And you can use the move_uploaded_file() function to move the file from the temporary directory to another one.

The move_uploaded_file() function accepts two arguments:

  • filename: is the file name of the uploaded file which is $_FILES['file']['tmp_name'].
  • destination: is the destination of the moved file.

The move_uploaded_file() function returns true if it moves the file successfully; otherwise, it returns false.

Security measures

All the information in the $_FILES variable cannot be trusted except for the tmp_name. Hackers can manipulate the $_FILES and uploads the malicious script to the server.

To prevent this, you need to validate the information in the $_FILES.

First, check if the file input name is in the $_FILES variable by using the isset():

if(! isset($_FILES['file']) ) {
   // error
}Code language: PHP (php)

In this example, the 'file' is the name of the file input element.

Second, check the actual size of the file by calling the filesize() function and compare its result with the maximum allowed file size. It should not trust the size provided by the $_FILES. For example:

const MAX_SIZE  = 5 * 1024 * 1024; //  5MB

if (filesize($_FILES['file']['tmp_name']) > MAX_SIZE) {
   // error
}Code language: PHP (php)

Note that the MAX_SIZE must not be greater than upload_max_filesize specified in the php.ini.

The size of a file is in bytes, which is not human-readable. To make it more readable, we can define a function that converts the bytes to a human-readable format e.g., 1.20M, 2.51G:

function format_filesize(int $bytes, int $decimals = 2): string
{
    $units = 'BKMGTP';
    $factor = floor((strlen($bytes) - 1) / 3);

    return sprintf("%.{$decimals}f", $bytes / pow(1024, $factor)) . $units[(int)$factor];
}
Code language: PHP (php)

Third, validate the MIME type of the file against the allowed file types. To do this, you need to define a list of allowed files:

const ALLOWED_FILES = [
   'image/png' => 'png',
   'image/jpeg' => 'jpg'
];Code language: PHP (php)

To get the real mime type of a file, you use three functions: finfo_open(), finfo_file(), and finfo_close().

  • The finfo_open() returns a new fileinfo resource.
  • The finfo_file() returns the information about the file.
  • The finfo_close() closes the fileinfo resource.

To make it easy and reusable, you can define a function get_mime_type() like this:

function get_mime_type(string $filename)
{
    $info = finfo_open(FILEINFO_MIME_TYPE);
    if (!$info) {
        return false;
    }

    $mime_type = finfo_file($info, $filename);
    finfo_close($info);

    return $mime_type;
}
Code language: PHP (php)

The get_mime_type() function accepts a filename and returns the MIME type of the file. It’ll return false if an error occurs.

Note that the Internet Assigned Numbers Authority (IANA) is in charge of all official MIME types, and you can find the complete list on their MIME type page.

If an error occurs or validation fails, you can set a flash message and redirect the browser back to the upload page. The following function sets a flash message and performs a redirection:

function redirect_with_message(string $message, string $type=FLASH_ERROR, string $name='upload', string $location='index.php'): void
{
    flash($name, $message, $type);
    header("Location: $location", true, 303);
    exit;
}Code language: PHP (php)

Note that we use the flash() function defined in the flash.php file. The flash() function shows a session-based flash message. Check out the flash message tutorial here.

The following shows how to use the redirection_with_message() function:

if(error) {
   redirect_with_message('An error occurred');
}Code language: JavaScript (javascript)

The return statement ends the current script.

Since all of these functions get_mime_type(), format_filesize(), and redirect_with_message() are reusable, you can add them to the functions.php file like this:

<?php

/**
 *  Messages associated with the upload error code
 */
const MESSAGES = [
    UPLOAD_ERR_OK => 'File uploaded successfully',
    UPLOAD_ERR_INI_SIZE => 'File is too big to upload',
    UPLOAD_ERR_FORM_SIZE => 'File is too big to upload',
    UPLOAD_ERR_PARTIAL => 'File was only partially uploaded',
    UPLOAD_ERR_NO_FILE => 'No file was uploaded',
    UPLOAD_ERR_NO_TMP_DIR => 'Missing a temporary folder on the server',
    UPLOAD_ERR_CANT_WRITE => 'File is failed to save to disk.',
    UPLOAD_ERR_EXTENSION => 'File is not allowed to upload to this server',
];

/**
 * Return a mime type of file or false if an error occurred
 *
 * @param string $filename
 * @return string | bool
 */
function get_mime_type(string $filename)
{
    $info = finfo_open(FILEINFO_MIME_TYPE);
    if (!$info) {
        return false;
    }

    $mime_type = finfo_file($info, $filename);
    finfo_close($info);

    return $mime_type;
}

/**
 * Return a human-readable file size
 *
 * @param int $bytes
 * @param int $decimals
 * @return string
 */
function format_filesize(int $bytes, int $decimals = 2): string
{
    $units = 'BKMGTP';
    $factor = floor((strlen($bytes) - 1) / 3);

    return sprintf("%.{$decimals}f", $bytes / pow(1024, $factor)) . $units[(int)$factor];
}


/**
 * Redirect user with a session based flash message
 * @param string $message
 * @param string $type
 * @param string $name
 * @param string $location
 * @return void
 */
function redirect_with_message(string $message, string $type=FLASH_ERROR, string $name='upload', string $location='index.php'): void
{
    flash($name, $message, $type);
    header("Location: $location", true, 303);
    exit;
}Code language: HTML, XML (xml)

Using MAX_FILE_SIZE form field

If you place a field with the name MAX_FILE_SIZE before a file input element in the form, PHP will use that value instead of upload_max_filesize for validating the file size.

For example:

<form enctype="multipart/form-data" action="upload.php" method="post">
    <div>
        <label for="file">Select a file:</label>
        <input type="hidden" name="MAX_FILE_SIZE" value="10240"/>
        <input type="file" id="file" name="file"/>
    </div>
    <div>
        <button type="submit">Upload</button>
    </div>
</form>Code language: HTML, XML (xml)

In this example, the MAX_FILE_SIZE is 10KB. If you upload a file that is larger than 10KB PHP will issue an error. However, it’s easy to manipulate this field so you should never rely on it for security purposes.

Note that you cannot set the MAX_FILE_SIZE larger than the upload_max_filesize directive in the php.ini file.

PHP file upload example

First, create the following directory structure:

├── inc
|  ├── flash.php
|  └── functions.php
├── index.php
├── upload.php
└── uploadsCode language: plaintext (plaintext)

Second, add the following file upload form to the index.php file:

<?php
session_start();
require_once __DIR__ . '/inc/flash.php';
?>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
    <link rel="stylesheet" href="https://www.phptutorial.net/app/css/style.css"/>
    <title>PHP File Upload</title>
</head>
<body>

<?php flash('upload') ?>

<main>
    <form enctype="multipart/form-data" action="upload.php" method="post">
        <div>
            <label for="file">Select a file:</label>
            <input type="file" id="file" name="file"/>
        </div>
        <div>
            <button type="submit">Upload</button>
        </div>
    </form>
</main>
</body>
</html>Code language: PHP (php)

The index.php file also contains a form for uploading a file. The upload.php file will handle the upload.

Third, add the following code to the upload.php file to process the uploaded file:

<?php

session_start();

require_once __DIR__ . '/inc/flash.php';
require_once __DIR__ . '/inc/functions.php';

const ALLOWED_FILES = [
    'image/png' => 'png',
    'image/jpeg' => 'jpg'
];

const MAX_SIZE = 5 * 1024 * 1024; //  5MB

const UPLOAD_DIR = __DIR__ . '/uploads';


$is_post_request = strtolower($_SERVER['REQUEST_METHOD']) === 'post';
$has_file = isset($_FILES['file']);

if (!$is_post_request || !$has_file) {
    redirect_with_message('Invalid file upload operation', FLASH_ERROR);
}

//
$status = $_FILES['file']['error'];
$filename = $_FILES['file']['name'];
$tmp = $_FILES['file']['tmp_name'];


// an error occurs
if ($status !== UPLOAD_ERR_OK) {
    redirect_with_message($messages[$status], FLASH_ERROR);
}

// validate the file size
$filesize = filesize($tmp);
if ($filesize > MAX_SIZE) {
    redirect_with_message('Error! your file size is ' . format_filesize($filesize) . ' , which is bigger than allowed size ' . format_filesize(MAX_SIZE), FLASH_ERROR);
}

// validate the file type
$mime_type = get_mime_type($tmp);
if (!in_array($mime_type, array_keys(ALLOWED_FILES))) {
    redirect_with_message('The file type is not allowed to upload', FLASH_ERROR);
}
// set the filename as the basename + extension
$uploaded_file = pathinfo($filename, PATHINFO_FILENAME) . '.' . ALLOWED_FILES[$mime_type];
// new file location
$filepath = UPLOAD_DIR . '/' . $uploaded_file;

// move the file to the upload dir
$success = move_uploaded_file($tmp, $filepath);
if ($success) {
    redirect_with_message('The file was uploaded successfully.', FLASH_SUCCESS);
}

redirect_with_message('Error moving the file to the upload folder.', FLASH_ERROR);
Code language: PHP (php)

How the upload.php works.

1) Start the session and include the flash.php and functions.php files so that we can use the utility functions defined above:

session_start();
require_once __DIR__ . '/inc/flash.php';
require_once __DIR__ . '/inc/functions.php';Code language: PHP (php)

2) Define an array that specifies the allowed files:

const ALLOWED_FILES = [
   'image/png' => 'png',
   'image/jpeg' => 'jpg'
];Code language: PHP (php)

3) Define a constant that specifies the max file size:

const MAX_SIZE  = 5 * 1024 * 1024; //  5MBCode language: JavaScript (javascript)

3) Define the upload directory that stores the uploaded files:

const UPLOAD_DIR = __DIR__ . '/uploads';Code language: PHP (php)

4) Return an error message if the request method is not POST or the file does not exist in the $_FILES variable:

$is_post_request = strtolower($_SERVER['REQUEST_METHOD']) === 'post';
$has_file = isset($_FILES['file']);

if (!$is_post_request || !$has_file) {
    redirect_with_message('Invalid file upload operation', FLASH_ERROR);
}
Code language: PHP (php)

5) Get the uploaded file information including error, filename, and temporary filename:

$status = $_FILES['file']['error'];
$filename = $_FILES['file']['name'];
$tmp = $_FILES['file']['tmp_name'];Code language: PHP (php)

6) Return an error message if the file was failed to upload:

if ($status !== UPLOAD_ERR_OK) {
    redirect_with_message(MESSAGES[$status], FLASH_ERROR);
}Code language: PHP (php)

7) Get the size of the file in the temporary folder and compare it with the MAX_SIZE. If the size of the uploaded file is greater than MAX_SIZE, issue an error:

// validate the file size
$filesize = filesize($tmp);
if ($filesize > MAX_SIZE) {
    redirect_with_message('Error! your file size is ' . format_filesize($filesize) . ' , which is bigger than allowed size ' . format_filesize(MAX_SIZE), FLASH_ERROR);
}Code language: PHP (php)

8) Get the MIME type and compare it with the MIME type of the allowed files specified in the ALLOWED_FILES array; issue an error if the validation fails:

$mime_type = get_mime_type($tmp);
if (!in_array($mime_type, array_keys(ALLOWED_FILES))) {
    redirect_with_message('The file type is not allowed to upload', FLASH_ERROR);
}Code language: PHP (php)

9) Construct a new filename by concatenating the filename from the uploaded file with the valid file extension.

// set the filename as the basename + extension
$uploaded_file = pathinfo($filename, PATHINFO_FILENAME) . '.' . ALLOWED_FILES[$mime_type];Code language: PHP (php)

Note that the following pathinfo() returns the filename without the extension:

pathinfo($filename, PATHINFO_FILENAME)Code language: PHP (php)

For example, if the $filename is example.jpg, it’ll return the example only.

10) Move the file from the temp directory to the upload folder and issue an error or success message depending on the result of the move_uploaded_file() function:

$filepath = UPLOAD_DIR . '/' . $uploaded_file;

// move the file to the upload dir
$success = move_uploaded_file($tmp, $filepath);

if ($success) {
    redirect_with_message('The file was uploaded successfully.', FLASH_SUCCESS);
}

redirect_with_message('Error moving the file to the upload directory.', FLASH_ERROR);Code language: PHP (php)

Summary

  • Use the input with type="file" to create a file input element and include the enctype="multipart/form-data" attribute in the form to allow the file upload.
  • Access the uploaded file information via the $_FILES array.
  • Never trust the information on the $_FILES except the tmp_name .
  • Always validate the information on the $_FILES .
  • Use the move_uploaded_file() function to move the file from the temporary directory to another folder.
Did you find this tutorial useful?