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:
<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:
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.ini
Code 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=20
Code 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 orapplication/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, thetmp_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; // 5MBif (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 newfileinfo
resource. - The
finfo_file()
returns the information about the file. - The
finfo_close()
closes thefileinfo
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:
/**
* 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
└── uploads
Code language: plaintext (plaintext)
Second, add the following file upload form to the index.php
file:
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>'upload')
flash(<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:
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; // 5MB
Code 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
withtype="file"
to create a file input element and include theenctype="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 thetmp_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.