Asynchronous file upload gives the user a more seamless experience by allowing them to upload files without having to reload the page.
Handle the file on the client side using jQuery and JavaScript, send the file to the server using an AJAX request, and then process the file on the server.
If the file is not validated then you can immediately display the error messages.
In this tutorial, I show how you can upload file using jQuery AJAX with validation and display a preview of it after uploading it in CakePHP 4.

Contents
1. Include jQuery and CSRF token
I am including jQuery library and CSRF token in templates/layout/default.php file.
Completed Code
<?php
$cakeDescription = 'CakePHP: the rapid development php framework';
?>
<!DOCTYPE html>
<html>
<head>
<?= $this->Html->charset() ?>
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- CSRF Token -->
<?= $this->Html->meta('csrfToken', $this->request->getAttribute('csrfToken')); ?>
<title>
<?= $cakeDescription ?>:
<?= $this->fetch('title') ?>
</title>
<?= $this->Html->meta('icon') ?>
<link href="https://fonts.googleapis.com/css?family=Raleway:400,700" rel="stylesheet">
<?= $this->Html->css(['normalize.min', 'milligram.min', 'cake']) ?>
<!-- jQuery -->
<?= $this->Html->script('https://code.jquery.com/jquery.min.js'); ?>
<?= $this->fetch('meta') ?>
<?= $this->fetch('css') ?>
<?= $this->fetch('script') ?>
</head>
<body>
<nav class="top-nav">
<div class="top-nav-title">
<a href="<?= $this->Url->build('/') ?>"><span>Cake</span>PHP</a>
</div>
<div class="top-nav-links">
<a target="_blank" rel="noopener" href="https://book.cakephp.org/4/">Documentation</a>
<a target="_blank" rel="noopener" href="https://api.cakephp.org/">API</a>
</div>
</nav>
<main class="main">
<div class="container">
<?= $this->Flash->render() ?>
<?= $this->fetch('content') ?>
</div>
</main>
<footer>
</footer>
</body>
</html>
2. Create Controller
- Create a
FileuploadController.phpfile insrc/Controller/folder. - Create
FileuploadControllerClass that extendsAppController. - Include
Cake\Filesystem\Folderto create folder andCake\Validation\Validatorto add validation rules.
Create 2 methods –
- index()
- upload() – Using this method handle AJAX file upload request.
Define validation rule for upload_file. Here, upload_file is POST file element name. Customize the validation according to your requirement.
If file is not validated then read the error message and assign it to $response['error']. Also, assign 0 to $response['success']. Return $response Array in JSON format.
If file is validated then read file details and assign them to the variables. Assign upload location to $location variable. I am uploading file to uploads folder. Check if a folder exists or not. Create it if not exists.
Upload the file to the location by calling moveTo(). After upload assign the upload path to $filepath variable.
Store filename, path, success message, and success status in $response Array. Return $response Array in JSON format.
NOTE – You can find the uploaded files in
webroot/uploads/location.
Completed Code
<?php
declare(strict_types=1);
namespace App\Controller;
use Cake\Filesystem\Folder;
use Cake\Validation\Validator;
class FileuploadController extends AppController
{
public function index(){
}
// Upload file
public function upload(){
## Validate File
$validator = new Validator();
$validator
->notEmptyFile('upload_file')
->add('upload_file', [
'mimeType' => [
'rule' => ['mimeType',['image/jpg','image/png','image/jpeg','application/pdf']],
'message' => 'File type must be .jpg,.jpeg,.png,.pdf',
],
'fileSize' => [
'rule' => ['fileSize','<', '2MB'],
'message' => 'File size must be less than 2MB',
]
]);
$errors = $validator->validate($this->request->getData());
$response = array();
if (!empty($errors)) { // Not validated
## Return response with error message
$fileerrors = $errors['upload_file'];
$errormsg = "";
foreach($fileerrors as $error){
$errormsg = $error;
}
$response['error'] = $errormsg;
$response['success'] = 0;
echo json_encode($response);
die;
}
## Upload file
$fileEl = $this->request->getData('upload_file');
// File details
$filename = $fileEl->getClientFilename();
$type = $fileEl->getClientMediaType();
$size = $fileEl->getSize();
$extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
$tmpName = $fileEl->getStream()->getMetadata('uri');
$error = $fileEl->getError();
if($error == 0){
## Upload location
$location = WWW_ROOT . 'uploads' . DS;// webroot/uploads/
## Check upload location exists or not
$folder = new Folder();
$checkfolder = $folder->inPath($location, true);
if(!$checkfolder){ // Not exists
if (!$folder->create($location)) {
$response['error'] = 'Folder is not created.';
$response['success'] = 0;
echo json_encode($response);
die;
}
}
## Upload file
$targetPath = $location.$filename;
$fileEl->moveTo($targetPath);
## Uploaded file path
$filepath = "/uploads/".$filename;
## Return file details with response for preview
$imageext_arr = array('jpg','jpeg','png');
$isImage = 0;
if(in_array($extension,$imageext_arr)){
$isImage = 1;
}
$response['filename'] = $filename;
$response['path'] = $filepath;
$response['isImage'] = $isImage;
$response['msg'] = 'File uploaded successfully.';
$response['success'] = 1;
echo json_encode($response);
die;
}else{
$response['error'] = 'File is not uploaded.';
$response['success'] = 0;
echo json_encode($response);
die;
}
}
}
3. Create Template
Create Fileupload folder in templates/ location. In the Fileupload folder create index.php file – templates/Fileupload/index.php.
Create a file element and a button. To display file preview after upload create <div id="div_filepreview">.
Script
Read CSRF token from <meta > tag and assign it to csrfToken variable.
Define click event on #btnupload. On button click read selected file from the file element and assign it to files variable. If files.length is not > 0 means file is not selected and display an alert message.
If files.length>0 then create FormData object. Append selected file in fd object.
Send AJAX POST request to <?= $this->Url->build(['controller' => 'fileupload','action' => 'upload']) ?>. Pass FormData object as data, set contentType and processData to false.
Using header to set csrfToken – 'X-CSRF-Token': csrfToken.
On successful callback check if response.success value. If response.success == 0 means file is not uploaded and alert the error message.
Completed Code
<div class="row">
<div class="col-6">
<?php
//
echo $this->Form->control('file',['label' => 'Select file','type' => 'file','class' => 'form-control','id' => 'upload_file']);
echo $this->Form->button('Upload',['id'=>'btnupload']);
?>
<!-- Display file preview -->
<div id="div_filepreview">
</div>
</div>
</div>
<!-- Script -->
<script type="text/javascript">
// Read CSRF Token
var csrfToken = $('meta[name="csrfToken"]').attr('content');
$(document).ready(function(){
$('#btnupload').click(function(){
var files = $('#upload_file')[0].files;
// Check file selected or not
if(files.length > 0 ){
var fd = new FormData();
fd.append('upload_file',files[0]);
$.ajax({
url: "<?= $this->Url->build(['controller' => 'fileupload','action' => 'upload']) ?>",
type: 'post',
data: fd,
contentType: false,
processData: false,
headers:{
'X-CSRF-Token': csrfToken
},
dataType: 'json',
success: function(response){
if(response.success == 0){ // Not validated
alert(response.error);
}else{
// Display preview
var filename = response.filename;
var path = response.path;
var isImage = response.isImage;
if(isImage == 1){
$('#div_filepreview').html("<img src='"+path+"' width='200px' >");
}else{
$('#div_filepreview').html("<a href='"+path+"' target='_blank' >"+ filename +"</a>");
}
}
},
});
}else{
alert("Please select a file.");
}
});
});
</script>
4. Output
5. Conclusion
Use FormData object to pass the selected file for upload. You can also pass additional data by appending it to the FormData object – fd.append('filename','new-filename');.
If you want to upload a file without using AJAX in CakePHP 4, check out this tutorial.
If you found this tutorial helpful then don't forget to share.