A MySQL BLOB column stores binary data such as an image. With PDO, bind the binary value to a prepared statement using PDO::PARAM_LOB.
require 'config.php';
$id=8;
$student='Alex';
$photo=fopen(
'photos/1.png',
'rb'
);
$sql="INSERT INTO student_profile
(id,student,profile_photo)
VALUES
(:id,:student,:profile_photo)";
$stmt=$dbo->prepare($sql);
$stmt->bindValue(
':id',
$id,
PDO::PARAM_INT
);
$stmt->bindValue(
':student',
$student,
PDO::PARAM_STR
);
$stmt->bindParam(
':profile_photo',
$photo,
PDO::PARAM_LOB
);
$stmt->execute();
fclose($photo);
This combines a prepared INSERT statement with PDO large-object binding.
BLOB means Binary Large Object. It is used for binary data that should not be treated as ordinary character text.
Examples include:
MySQL provides several BLOB types with different capacities. See the MySQL BLOB data type tutorial for the SQL datatype itself.
The existing Plus2net example uses three columns:
| Column | Type | Purpose |
|---|---|---|
id | INT | Student ID |
student | VARCHAR | Student name |
profile_photo | BLOB | Binary image data |
The profile record can be associated with the existing Plus2net student table.
BLOB stores only up to 65,535 bytes. Many modern images are larger. Use an appropriately sized binary type such as MEDIUMBLOB if the application needs larger files.This example reads a PNG file in binary mode and stores it in profile_photo.
require 'config.php';
$id=8;
$student='Alex';
$photo=fopen(
'photos/1.png',
'rb'
);
if($photo===false){
exit('Unable to open image file.');
}
try{
$sql="INSERT INTO student_profile
(id,student,profile_photo)
VALUES
(:id,:student,:profile_photo)";
$stmt=$dbo->prepare($sql);
$stmt->bindValue(
':id',
$id,
PDO::PARAM_INT
);
$stmt->bindValue(
':student',
$student,
PDO::PARAM_STR
);
$stmt->bindParam(
':profile_photo',
$photo,
PDO::PARAM_LOB
);
$stmt->execute();
echo 'Photo was stored.';
}catch(PDOException $e){
error_log($e->getMessage());
echo 'Unable to save the photo.';
}finally{
fclose($photo);
}
The binary stream is supplied through PDO::PARAM_LOB instead of being concatenated into the SQL statement.
Binary data can also be read into a PHP string:
$photo=file_get_contents(
'photos/1.png'
);
if($photo===false){
exit('Unable to read image.');
}
$stmt->bindValue(
':profile_photo',
$photo,
PDO::PARAM_LOB
);
file_get_contents() is simple for small files but loads the complete file into PHP memory.
Using:
$photo=fopen(
'photos/1.png',
'rb'
);
provides a stream resource. This can be preferable when working with larger binary values, although actual buffering behavior can also depend on the PDO driver.
Select the binary field just like another database column.
$id=8;
$stmt=$dbo->prepare(
"SELECT id,student,profile_photo
FROM student_profile
WHERE id=:id"
);
$stmt->bindValue(
':id',
$id,
PDO::PARAM_INT
);
$stmt->execute();
$row=$stmt->fetch(
PDO::FETCH_ASSOC
);
This uses a prepared SELECT query and WHERE condition.
For a small image, the binary data can be Base64-encoded and placed inside an HTML data URI.
If the stored sample is PNG:
if($row && $row['profile_photo']!==null){
$image=base64_encode(
$row['profile_photo']
);
echo "<img
src='data:image/png;base64,$image'
alt='Student profile photo'
class='img-fluid'>";
}
image/jpeg. The old version of this tutorial mixed these two formats; this has now been corrected.$sql="SELECT id,student,profile_photo
FROM student_profile
ORDER BY id";
$stmt=$dbo->query($sql);
echo '<table class="table table-striped">';
echo '<tr><th>ID</th><th>Name</th><th>Photo</th></tr>';
while($row=$stmt->fetch(PDO::FETCH_ASSOC)){
$id=(int)$row['id'];
$student=htmlspecialchars(
(string)$row['student'],
ENT_QUOTES,
'Windows-1252'
);
$image='';
if($row['profile_photo']!==null){
$base64=base64_encode(
$row['profile_photo']
);
$image="<img
src='data:image/png;base64,$base64'
alt='Profile photo for $student'
width='100'>";
}
echo "<tr>
<td>$id</td>
<td>$student</td>
<td>$image</td>
</tr>";
}
echo '</table>';
The ORDER BY clause keeps the records in a predictable sequence.
Base64 data URIs are convenient for demonstrations, but they increase the encoded size and place the complete image inside the HTML document.
A separate image-response script can instead return the binary data directly.
<?php
require 'config.php';
$id=filter_input(
INPUT_GET,
'id',
FILTER_VALIDATE_INT,
[
'options' => [
'min_range' => 1
]
]
);
if($id===false || $id===null){
http_response_code(400);
exit;
}
$stmt=$dbo->prepare(
"SELECT profile_photo
FROM student_profile
WHERE id=:id"
);
$stmt->bindValue(
':id',
$id,
PDO::PARAM_INT
);
$stmt->execute();
$photo=$stmt->fetchColumn();
if($photo===false || $photo===null){
http_response_code(404);
exit;
}
header('Content-Type: image/png');
echo $photo;
exit;
This approach avoids Base64 expansion in the HTML page and lets the browser request the image as a separate resource.
image/png header because the sample data is PNG. If several formats are stored, save and validate the MIME type as part of the record.The original sample table stores only the binary data. That is sufficient only when the application already knows the format.
A more flexible table can include the MIME type:
CREATE TABLE student_profile_new (
id INT PRIMARY KEY,
student VARCHAR(50) NOT NULL,
profile_photo MEDIUMBLOB,
mime_type VARCHAR(50)
);
When an uploaded file is validated, store the server-detected MIME type:
$sql="INSERT INTO student_profile_new
(id,student,profile_photo,mime_type)
VALUES
(:id,:student,:profile_photo,:mime_type)";
$stmt=$dbo->prepare($sql);
$stmt->bindValue(
':mime_type',
$mime,
PDO::PARAM_STR
);
When serving the file, do not blindly send any arbitrary value as an HTTP header. Validate the stored value against the MIME types your application supports.
$allowed_mimes=[
'image/jpeg',
'image/png',
'image/webp'
];
if(
!in_array(
$row['mime_type'],
$allowed_mimes,
true
)
){
http_response_code(415);
exit;
}
header(
'Content-Type: '.
$row['mime_type']
);
Updating binary data uses the same LOB binding technique with an UPDATE statement.
$id=1;
$photo=fopen(
'photos/2.png',
'rb'
);
if($photo===false){
exit('Unable to open replacement image.');
}
try{
$sql="UPDATE student_profile
SET profile_photo=:profile_photo
WHERE id=:id";
$stmt=$dbo->prepare($sql);
$stmt->bindValue(
':id',
$id,
PDO::PARAM_INT
);
$stmt->bindParam(
':profile_photo',
$photo,
PDO::PARAM_LOB
);
$stmt->execute();
echo 'Photo was updated.';
}catch(PDOException $e){
error_log($e->getMessage());
echo 'Unable to update the photo.';
}finally{
fclose($photo);
}
For a single independent UPDATE, an explicit transaction is normally unnecessary. Use a transaction when the BLOB update must succeed together with other dependent database changes.
PDO UPDATEIf the schema permits a profile without an image, store an actual SQL NULL rather than an arbitrary empty string.
$photo=null;
$stmt=$dbo->prepare(
"UPDATE student_profile
SET profile_photo=:photo
WHERE id=:id"
);
$stmt->bindValue(
':photo',
null,
PDO::PARAM_NULL
);
$stmt->bindValue(
':id',
1,
PDO::PARAM_INT
);
$stmt->execute();
The column itself must allow NULL for this example. See the SQL NULL tutorial for NULL handling at the database level.
| MySQL type | Maximum size | Typical use |
|---|---|---|
TINYBLOB | 255 bytes | Very small binary values |
BLOB | 65,535 bytes | Small binary files |
MEDIUMBLOB | 16,777,215 bytes | Larger images/documents |
LONGBLOB | Up to about 4 GB | Very large binary values where practical |
The theoretical datatype limit does not mean the application can automatically transfer a file of that size. PHP memory limits, upload settings, MySQL packet limits and application design can impose much smaller practical limits.
Binary data can be much larger than ordinary database fields, so retrieve it only when needed.
A page that needs only student names should not use:
SELECT *
FROM student_profile;
if that also transfers every stored photo.
Prefer:
SELECT id,student
FROM student_profile
ORDER BY id;
and load the BLOB only when the image is actually requested.
Base64 encoding increases the amount of text required to represent the binary file. It is convenient for small tutorial examples but is not automatically the best approach for a page containing many or large images.
Both designs can be valid depending on the application.
| Database BLOB | File/Object Storage |
|---|---|
| Binary data remains with the database record | Database stores only a path, key or URL |
| Can simplify some backup and permission workflows | Often easier for web servers/CDNs to deliver media |
| Database size can grow quickly | Database remains smaller |
| Useful for tightly controlled binary records | Often practical for large volumes of images/files |
There is no rule that every uploaded image should be a BLOB. Choose the storage model according to the application's access, backup, performance and delivery requirements.
When the binary data comes from a visitor rather than a known local file, validate the upload before sending it to the database.
The upload workflow is:
Browser form
|
v
$_FILES
|
v
Check upload error
|
v
Check size
|
v
Detect MIME type
|
v
Validate image
|
v
Open binary stream
|
v
PDO::PARAM_LOB
|
v
MySQL BLOB
Upload Image and Store in MySQL BLOB
A PNG BLOB must not be sent as image/jpeg. Either use one known format or store the validated MIME type with each record.
A normal BLOB is limited to about 64 KB. Use a larger datatype or another storage strategy for larger files.
Binary columns can be large. Do not retrieve them on listing pages when the page needs only ordinary fields.
Log PDO exceptions on the server and show visitors a short message rather than exposing errorInfo().
The BLOB itself may be binary, but fields such as student names must still be escaped before inserting them into HTML.
Base64 is useful for small examples but increases data size and places the binary content inside the HTML. Direct image responses are often a better design for larger media.
When a binary file is opened using fopen(), close the resource after the database operation.
PDO::PARAM_LOB controls parameter handling. It does not check file type, file size, upload validity or application authorization.
Prepare an INSERT or UPDATE statement and bind the binary string or file stream to a placeholder using PDO::PARAM_LOB.
A standard MySQL BLOB can store up to 65,535 bytes. Larger data can use MEDIUMBLOB or LONGBLOB subject to other database and application limits.
For small images you can Base64-encode the binary value into a data URI. Another approach is to return the binary data from a dedicated response with the correct Content-Type header.
If records can contain different image or file formats, the validated MIME type tells the application which Content-Type should be used when returning the binary data.
file_get_contents() loads the complete file into a PHP string. fopen() provides a stream resource that can be bound as PDO::PARAM_LOB and can be more suitable for larger binary values.
No. BLOB storage is useful for some applications, while file-system or object storage with a database path or key is often more practical for large quantities of media.
Not unless the application needs every column. Avoid retrieving large binary fields on pages that only require ordinary metadata such as IDs and names.
Author & Instructor at plus2net
I write and maintain practical tutorials on Python, PHP, SQL, JavaScript, HTML, jQuery, and web development at plus2net. The tutorials focus on clear explanations, working examples, and code that readers can test and adapt while learning.