
PHP File Upload with MySQL
File upload is a common feature in web applications. Users may need to upload profile images, resumes, PDF documents, product images, attachments, certificates, or gallery photos. PHP can receive uploaded files, validate them, move them to a safe folder, and store their information in a MySQL database.
When file upload is combined with MySQL, the uploaded file itself is usually stored in a folder on the server, while the database stores information such as the file name, file path, file type, file size, and upload date.
This article explains PHP file upload with MySQL in detail, including HTML upload forms, PHP upload handling, validation, file type checks, file size checks, safe file names, storing file paths in MySQL, displaying uploaded files, updating uploaded files, deleting uploaded files, and security best practices.
How PHP File Upload Works
When a user selects a file and submits a form, the browser sends the file to the server. PHP receives the uploaded file through the $_FILES superglobal.
The upload process usually follows these steps:
The user selects a file from an HTML form.
The form sends the file using POST and multipart/form-data.
PHP reads the uploaded file from $_FILES.
PHP validates the upload error status, size, extension, and MIME type.
PHP generates a safe new file name.
PHP moves the file from temporary storage to a permanent upload folder.
PHP stores the file information in MySQL.
The application can later display, download, update, or delete the file.
This workflow is simple in concept, but file upload must be handled carefully because uploaded files can create serious security risks if validation is weak.
Database Table for Uploaded Files
Before storing file information in MySQL, you need a database table. A common table name is uploads or files.
CREATE TABLE uploads (
id INT AUTO_INCREMENT PRIMARY KEY,
original_name VARCHAR(255) NOT NULL,
stored_name VARCHAR(255) NOT NULL,
file_path VARCHAR(500) NOT NULL,
mime_type VARCHAR(150) NOT NULL,
file_size INT NOT NULL,
extension VARCHAR(20) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);The original_name column stores the original name uploaded by the user. The stored_name column stores the safe generated file name used on the server.
The file_path column stores the relative path to the file. The mime_type column stores the detected file type. The file_size column stores the file size in bytes.
It is usually better to store the file path in the database instead of storing the full file content directly inside MySQL. Storing files on disk and metadata in the database is simpler and more efficient for many web applications.
PDO Database Connection
PHP can connect to MySQL using PDO. PDO supports prepared statements, which are important for safely inserting uploaded file information into the database.
Create a file named db.php:
<?php
$host = "localhost";
$dbname = "php_uploads";
$username = "root";
$password = "";
try {
$pdo = new PDO(
"mysql:host=$host;dbname=$dbname;charset=utf8mb4",
$username,
$password
);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
} catch (PDOException $exception) {
die("Database connection failed.");
}
?>In production, database credentials should be stored in environment variables or configuration files outside the public directory.
The utf8mb4 charset is recommended because it supports multilingual text correctly.
HTML File Upload Form
To upload files, the HTML form must use method="post" and enctype="multipart/form-data". Without the correct enctype, the file will not be sent properly.
<form method="post" action="upload.php" enctype="multipart/form-data">
<label>Choose file</label>
<input type="file" name="uploaded_file">
<button type="submit">Upload</button>
</form>The input name uploaded_file will be used by PHP to access the file from $_FILES["uploaded_file"].
For image-only uploads, the form can use the accept attribute to improve user experience.
<input type="file" name="uploaded_file" accept="image/jpeg,image/png,image/webp">The accept attribute helps the browser show relevant file types, but it is not a security feature. PHP must still validate the file on the server side.
Understanding $_FILES
When a file is uploaded, PHP stores information about it in the $_FILES array.
<?php
print_r($_FILES["uploaded_file"]);
?>The uploaded file information usually includes:
name: the original file name from the user's computer.
type: the MIME type sent by the browser.
tmp_name: the temporary location of the uploaded file on the server.
error: the upload error code.
size: the file size in bytes.
The tmp_name value is important because PHP first stores uploaded files temporarily. To keep the file permanently, you must move it using move_uploaded_file().
Basic PHP File Upload
A basic upload script receives the file and moves it to an uploads folder.
<?php
if ($_SERVER["REQUEST_METHOD"] === "POST") {
$tmpName = $_FILES["uploaded_file"]["tmp_name"];
$fileName = $_FILES["uploaded_file"]["name"];
move_uploaded_file($tmpName, __DIR__ . "/uploads/" . $fileName);
echo "File uploaded.";
}
?>This simple example shows the basic idea, but it is not secure enough for real applications. It uses the original file name and does not validate the file type, size, or upload error.
In real projects, never trust the original file name or file extension without validation. A secure upload system should generate a new file name and validate the uploaded file carefully.
Checking Upload Errors
PHP provides upload error codes through $_FILES["file"]["error"]. Always check this value before processing the uploaded file.
<?php
$error = $_FILES["uploaded_file"]["error"];
if ($error !== UPLOAD_ERR_OK) {
echo "Upload failed.";
exit;
}
?>Common upload errors include:
UPLOAD_ERR_OK: upload completed successfully.
UPLOAD_ERR_INI_SIZE: file exceeds the upload_max_filesize setting in php.ini.
UPLOAD_ERR_FORM_SIZE: file exceeds the MAX_FILE_SIZE value from the form.
UPLOAD_ERR_PARTIAL: file was only partially uploaded.
UPLOAD_ERR_NO_FILE: no file was uploaded.
Checking upload errors helps you return better messages and avoid processing incomplete or invalid files.
Validating File Size
File size validation prevents users from uploading files that are too large. Large files can waste storage, slow down the server, or exceed hosting limits.
The following example allows files up to 2 MB:
<?php
$maxSize = 2 * 1024 * 1024;
$fileSize = $_FILES["uploaded_file"]["size"];
if ($fileSize > $maxSize) {
echo "File is too large. Maximum size is 2 MB.";
exit;
}
?>The file size is measured in bytes. The expression 2 * 1024 * 1024 means 2 megabytes.
In real projects, allowed file size depends on the use case. Profile images may be limited to 1 or 2 MB, while PDF documents or resumes may allow larger sizes.
Validating File Extension
The file extension is the part after the last dot in the file name, such as jpg, png, pdf, or docx. Extension validation helps restrict uploads to allowed file types.
<?php
$allowedExtensions = ["jpg", "jpeg", "png", "webp", "pdf"];
$originalName = $_FILES["uploaded_file"]["name"];
$extension = strtolower(pathinfo($originalName, PATHINFO_EXTENSION));
if (!in_array($extension, $allowedExtensions, true)) {
echo "This file extension is not allowed.";
exit;
}
?>Extension validation is useful, but it should not be the only check. A file can be renamed with a fake extension. For stronger validation, also check the MIME type.
Validating MIME Type
MIME type describes the actual type of a file, such as image/jpeg, image/png, application/pdf, or image/webp. PHP can detect MIME type using finfo_file().
<?php
$finfo = new finfo(FILEINFO_MIME_TYPE);
$mimeType = $finfo->file($_FILES["uploaded_file"]["tmp_name"]);
$allowedMimeTypes = [
"image/jpeg",
"image/png",
"image/webp",
"application/pdf"
];
if (!in_array($mimeType, $allowedMimeTypes, true)) {
echo "This file type is not allowed.";
exit;
}
?>MIME type validation is stronger than trusting the browser-provided $_FILES["type"] value because it inspects the file content more directly.
For image uploads, you can also use image-specific checks such as getimagesize() to confirm that the file is a real image.
Generating Safe File Names
Using the original file name can be risky. It may contain spaces, special characters, duplicate names, or unsafe patterns. A better approach is to generate a new random file name.
<?php
$extension = "jpg";
$storedName = bin2hex(random_bytes(16)) . "." . $extension;
?>This creates a random file name that is difficult to guess and unlikely to conflict with existing files.
You can also add a prefix:
<?php
$storedName = "upload_" . time() . "_" . bin2hex(random_bytes(8)) . "." . $extension;
?>Safe file names help prevent overwriting existing files and reduce problems caused by user-provided file names.
Moving Uploaded Files
After validation, the uploaded file should be moved from its temporary location to a permanent upload directory using move_uploaded_file().
<?php
$uploadDir = __DIR__ . "/uploads/";
if (!is_dir($uploadDir)) {
mkdir($uploadDir, 0755, true);
}
$destination = $uploadDir . $storedName;
if (!move_uploaded_file($_FILES["uploaded_file"]["tmp_name"], $destination)) {
echo "Failed to move uploaded file.";
exit;
}
echo "File moved successfully.";
?>The function move_uploaded_file() is designed specifically for uploaded files and helps ensure that the file came from a valid upload operation.
The upload directory should have correct permissions. It should be writable by the application but should not allow execution of uploaded PHP scripts.
Storing File Path in MySQL
After the file is moved successfully, store its information in the database. Use a prepared statement to insert metadata safely.
<?php
require "db.php";
$originalName = $_FILES["uploaded_file"]["name"];
$fileSize = $_FILES["uploaded_file"]["size"];
$filePath = "uploads/" . $storedName;
$statement = $pdo->prepare("
INSERT INTO uploads (original_name, stored_name, file_path, mime_type, file_size, extension)
VALUES (:original_name, :stored_name, :file_path, :mime_type, :file_size, :extension)
");
$statement->execute([
"original_name" => $originalName,
"stored_name" => $storedName,
"file_path" => $filePath,
"mime_type" => $mimeType,
"file_size" => $fileSize,
"extension" => $extension
]);
echo "File uploaded and saved in database.";
?>The database now contains information about the uploaded file. This makes it possible to list files, display images, download documents, delete uploads, or connect files to users and records.
Complete Upload Script
The following example combines validation, safe naming, file moving, and database insertion into one upload script.
<?php
require "db.php";
if ($_SERVER["REQUEST_METHOD"] !== "POST") {
echo "Invalid request.";
exit;
}
if (!isset($_FILES["uploaded_file"])) {
echo "No file was uploaded.";
exit;
}
$file = $_FILES["uploaded_file"];
if ($file["error"] !== UPLOAD_ERR_OK) {
echo "Upload error.";
exit;
}
$maxSize = 2 * 1024 * 1024;
if ($file["size"] > $maxSize) {
echo "File is too large.";
exit;
}
$allowedExtensions = ["jpg", "jpeg", "png", "webp", "pdf"];
$allowedMimeTypes = [
"image/jpeg",
"image/png",
"image/webp",
"application/pdf"
];
$originalName = $file["name"];
$extension = strtolower(pathinfo($originalName, PATHINFO_EXTENSION));
if (!in_array($extension, $allowedExtensions, true)) {
echo "File extension is not allowed.";
exit;
}
$finfo = new finfo(FILEINFO_MIME_TYPE);
$mimeType = $finfo->file($file["tmp_name"]);
if (!in_array($mimeType, $allowedMimeTypes, true)) {
echo "File type is not allowed.";
exit;
}
$storedName = bin2hex(random_bytes(16)) . "." . $extension;
$uploadDir = __DIR__ . "/uploads/";
if (!is_dir($uploadDir)) {
mkdir($uploadDir, 0755, true);
}
$destination = $uploadDir . $storedName;
if (!move_uploaded_file($file["tmp_name"], $destination)) {
echo "Failed to save uploaded file.";
exit;
}
$filePath = "uploads/" . $storedName;
$statement = $pdo->prepare("
INSERT INTO uploads (original_name, stored_name, file_path, mime_type, file_size, extension)
VALUES (:original_name, :stored_name, :file_path, :mime_type, :file_size, :extension)
");
$statement->execute([
"original_name" => $originalName,
"stored_name" => $storedName,
"file_path" => $filePath,
"mime_type" => $mimeType,
"file_size" => $file["size"],
"extension" => $extension
]);
echo "File uploaded successfully.";
?>This script is much safer than a basic upload example because it validates errors, size, extension, MIME type, file name, upload directory, and database insertion.
Displaying Uploaded Files
After storing file information in MySQL, you can select records from the database and display them on a page.
<?php
require "db.php";
$statement = $pdo->query("
SELECT id, original_name, file_path, mime_type, file_size, created_at
FROM uploads
ORDER BY id DESC
");
$files = $statement->fetchAll();
?>
<h1>Uploaded Files</h1>
<?php foreach ($files as $file): ?>
<div>
<p><?php echo htmlspecialchars($file["original_name"], ENT_QUOTES, "UTF-8"); ?></p>
<p>Size: <?php echo (int) $file["file_size"]; ?> bytes</p>
<a href="<?php echo htmlspecialchars($file["file_path"], ENT_QUOTES, "UTF-8"); ?>" target="_blank">Open File</a>
</div>
<?php endforeach; ?>When displaying values from the database, use htmlspecialchars() to escape text output safely.
For images, you can display the uploaded file using an image tag if the MIME type is an image.
<?php if (str_starts_with($file["mime_type"], "image/")): ?>
<img src="<?php echo htmlspecialchars($file["file_path"], ENT_QUOTES, "UTF-8"); ?>" alt="" width="200">
<?php endif; ?>This makes it possible to build galleries, profile image systems, product image lists, or document management pages.
Downloading Uploaded Files
Sometimes you may not want users to access files directly through public links. Instead, you can create a download script that checks permissions first, then serves the file.
<?php
require "db.php";
$id = filter_input(INPUT_GET, "id", FILTER_VALIDATE_INT);
if (!$id) {
die("Invalid file ID.");
}
$statement = $pdo->prepare("SELECT * FROM uploads WHERE id = :id LIMIT 1");
$statement->execute(["id" => $id]);
$file = $statement->fetch();
if (!$file) {
die("File not found.");
}
$fullPath = __DIR__ . "/" . $file["file_path"];
if (!file_exists($fullPath)) {
die("File missing from server.");
}
header("Content-Type: " . $file["mime_type"]);
header("Content-Disposition: attachment; filename=\"" . basename($file["original_name"]) . "\"");
header("Content-Length: " . filesize($fullPath));
readfile($fullPath);
exit;
?>This approach is useful when files are private and should be downloaded only by authorized users.
Before serving private files, check whether the logged-in user has permission to access the file.
Updating an Uploaded File
In some applications, users need to replace an existing uploaded file. For example, a user may update a profile image or replace a resume document.
The update process usually follows these steps:
Find the existing file record in the database.
Validate the new uploaded file.
Move the new file to the upload directory.
Delete the old file from the server if needed.
Update the database record with the new file information.
A simplified update query can look like this:
<?php
$statement = $pdo->prepare("
UPDATE uploads
SET original_name = :original_name,
stored_name = :stored_name,
file_path = :file_path,
mime_type = :mime_type,
file_size = :file_size,
extension = :extension
WHERE id = :id
");
$statement->execute([
"id" => $id,
"original_name" => $originalName,
"stored_name" => $storedName,
"file_path" => $filePath,
"mime_type" => $mimeType,
"file_size" => $fileSize,
"extension" => $extension
]);
?>When replacing files, make sure you do not delete the old file until the new file is uploaded and saved successfully. This avoids losing the old file if the new upload fails.
Deleting Uploaded Files
Deleting an uploaded file should remove both the database record and the physical file from the server.
<?php
require "db.php";
$id = filter_input(INPUT_POST, "id", FILTER_VALIDATE_INT);
if (!$id) {
die("Invalid file ID.");
}
$statement = $pdo->prepare("SELECT * FROM uploads WHERE id = :id LIMIT 1");
$statement->execute(["id" => $id]);
$file = $statement->fetch();
if (!$file) {
die("File not found.");
}
$fullPath = __DIR__ . "/" . $file["file_path"];
$delete = $pdo->prepare("DELETE FROM uploads WHERE id = :id");
$delete->execute(["id" => $id]);
if (file_exists($fullPath)) {
unlink($fullPath);
}
echo "File deleted successfully.";
?>In real applications, delete actions should be protected by authentication, authorization, and CSRF protection.
If files are important, you may use soft delete logic instead of immediately deleting them permanently.
Multiple File Uploads
PHP can also handle multiple file uploads. The HTML input should use multiple and the name should use square brackets.
<form method="post" action="upload-multiple.php" enctype="multipart/form-data">
<input type="file" name="files[]" multiple>
<button type="submit">Upload Files</button>
</form>PHP receives multiple files as arrays inside $_FILES.
<?php
foreach ($_FILES["files"]["name"] as $index => $name) {
$tmpName = $_FILES["files"]["tmp_name"][$index];
$error = $_FILES["files"]["error"][$index];
$size = $_FILES["files"]["size"][$index];
if ($error !== UPLOAD_ERR_OK) {
continue;
}
// Validate and move each file here
}
?>Each file should be validated individually. Do not assume that all files are valid just because the form was submitted.
Multiple uploads are useful for galleries, product images, document sets, certificates, and attachments.
Connecting Uploads to Users or Posts
In real projects, uploaded files often belong to another record. For example, a profile image belongs to a user, a product image belongs to a product, and an attachment belongs to a message.
You can connect uploads to users by adding a user_id column.
ALTER TABLE uploads ADD user_id INT NULL;Then store the logged-in user's ID when inserting the upload:
<?php
session_start();
$userId = $_SESSION["user_id"];
$statement = $pdo->prepare("
INSERT INTO uploads (user_id, original_name, stored_name, file_path, mime_type, file_size, extension)
VALUES (:user_id, :original_name, :stored_name, :file_path, :mime_type, :file_size, :extension)
");
$statement->execute([
"user_id" => $userId,
"original_name" => $originalName,
"stored_name" => $storedName,
"file_path" => $filePath,
"mime_type" => $mimeType,
"file_size" => $fileSize,
"extension" => $extension
]);
?>This allows the application to show each user only their own uploaded files.
Always check ownership before displaying, downloading, updating, or deleting private uploaded files.
Image Upload Preview
For image upload forms, JavaScript can show a preview before the file is submitted. This improves user experience, but server-side validation is still required.
<input type="file" id="imageInput" name="uploaded_file" accept="image/*">
<img id="preview" src="" alt="" width="200" style="display:none;">
<script>
document.getElementById("imageInput").addEventListener("change", function () {
const file = this.files[0];
if (!file) {
return;
}
const preview = document.getElementById("preview");
preview.src = URL.createObjectURL(file);
preview.style.display = "block";
});
</script>This preview only helps the user see the selected image. It does not prove that the file is safe. PHP must still validate the file after upload.
PHP Configuration for Uploads
PHP upload behavior is also affected by server configuration. Important settings include:
file_uploads: enables or disables file uploads.
upload_max_filesize: maximum allowed uploaded file size.
post_max_size: maximum size of the entire POST request.
max_file_uploads: maximum number of files that can be uploaded in one request.
max_execution_time: maximum script execution time.
If your PHP script allows 10 MB files but upload_max_filesize is set to 2 MB, the server configuration will still reject larger files.
For larger upload features, adjust configuration carefully and avoid allowing unlimited file sizes.
Security Notes for PHP File Upload
File upload is one of the most sensitive features in web applications. A weak upload system can allow attackers to upload executable files, consume storage, or access private files.
Important security practices include:
Check upload errors before processing files.
Limit allowed file extensions.
Validate MIME type using finfo_file().
Limit file size.
Generate safe random file names.
Do not trust the original file name.
Do not allow PHP files or executable scripts to be uploaded.
Store private files outside the public directory when possible.
Prevent execution inside the uploads folder.
Check user permissions before download, update, or delete actions.
Use CSRF protection for upload and delete forms.
Escape file names when displaying them in HTML.
For public image uploads, it is also useful to resize images, strip unnecessary metadata, and store optimized versions.
How File Upload with MySQL Is Used in Real Projects
File upload with MySQL is used in many real web applications. The file is stored on the server or storage service, and the database keeps the metadata needed to manage it.
Common examples include:
User profile images.
Resume or CV uploads.
Product images.
Blog post cover images.
Document management systems.
Image galleries.
Certificates and academic documents.
Support ticket attachments.
Portfolio project images.
In larger systems, uploads may be stored in cloud storage such as Amazon S3 or another object storage service, while MySQL stores the file URL and metadata.
File Upload in Modern PHP Frameworks
Modern PHP frameworks such as Laravel provide clean tools for file upload, validation, storage, and database integration. Laravel can validate files, store them in local or cloud disks, generate paths, and connect uploads to models.
However, learning file upload in pure PHP is still important. It helps you understand how $_FILES, upload validation, move_uploaded_file(), file paths, and database metadata work behind the scenes.
Once you understand the pure PHP workflow, using framework tools becomes easier and clearer.
Conclusion
PHP file upload with MySQL is an important backend feature. It allows applications to receive files from users, validate them, store them safely on the server, and save file information in the database.
The key steps are creating an upload form, reading $_FILES, checking upload errors, validating file size, validating extension and MIME type, generating a safe file name, moving the file, and storing metadata in MySQL using prepared statements.
After learning this topic, the next step is to practice by building a profile image upload system, a document upload manager, a product image gallery, or a private file download system with user authentication and permissions.

