How to Upload Files in Chunks with FilePond and Laravel
What is FilePond?
FilePond is a modern JavaScript library that simplifies file uploads by providing drag-and-drop functionality, previews, validation, image editing, and support for large chunked uploads.
It supports chunked uploads, making it possible to upload large files in smaller pieces and resume interrupted uploads.
It is highly customizable, allowing developers to modify its appearance and behavior through options and plugins.
It integrates with popular frameworks, including Laravel, React, Vue, Angular, and plain JavaScript applications.
Filepond CDN
<link href="https://unpkg.com/filepond/dist/filepond.min.css" rel="stylesheet" />
<script src="https://unpkg.com/filepond/dist/filepond.min.js"></script>
Make a blade file upload.blade.php
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="csrf-token" content="{{ csrf_token() }}">
<title>{{ config('app.name', 'Laravel') }}</title>
<!-- Fonts -->
<link rel="preconnect" href="https://fonts.bunny.net">
<link href="https://fonts.bunny.net/css?family=instrument-sans:400,500,600" rel="stylesheet" />
<!-- Styles / Scripts -->
@if (file_exists(public_path('build/manifest.json')) || file_exists(public_path('hot')))
@vite(['resources/css/app.css', 'resources/js/app.js'])
@endif
<link href="https://unpkg.com/filepond/dist/filepond.min.css" rel="stylesheet" />
<script src="https://unpkg.com/filepond/dist/filepond.min.js"></script>
</head>
<body ">
<div>
<input type="file" class="filepond" name="file" />
</div>
<script>
const csrf = document.querySelector('meta[name="csrf-token"]').content;
FilePond.create(document.querySelector('input[type="file"]'), {
chunkUploads: true,
chunkSize: 5 * 1024 * 1024,
allowMultiple: false,
server: {
process: {
url: '/upload',
method: 'POST',
headers: {
'X-CSRF-TOKEN': csrf
}
},
patch: {
url: '/upload/',
method: 'PATCH',
headers: {
'X-CSRF-TOKEN': csrf,
'Accept': 'application/json'
}
},
revert: {
url: '/revert',
method: 'DELETE',
headers: {
'X-CSRF-TOKEN': csrf
}
}
}
});
</script>
</body>
</html>
Create a controller using an Artisan command.
php artisan make:controller UploadController
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
class UploadController extends Controller
{
public function process(Request $request)
{
$id = (string) Str::uuid();
Storage::disk('public')->makeDirectory("chunks/$id");
return response($id, 200)
->header('Content-Type', 'text/plain');
}
public function uploadChunk(Request $request, $id)
{
$path = storage_path("app/public/chunks/$id");
if (!file_exists($path)) {
mkdir($path, 0777, true);
}
$offset = $request->header('Upload-Offset', 0);
$length = $request->header('Upload-Length');
// FilePond sends RAW binary body here
$data = $request->getContent();
if ($data === null || $data === '') {
return response()->json([
'error' => 'Empty chunk received'
], 400);
}
file_put_contents("$path/$offset", $data);
// check progress
$uploaded = collect(glob("$path/*"))
->sum(fn($f) => filesize($f));
if ($length && $uploaded >= (int)$length) {
$this->mergeChunks($id);
}
return response()->noContent();
}
private function mergeChunks($id)
{
$chunkPath = storage_path("app/public/chunks/$id");
$finalDir = storage_path("app/public/videos");
if (!file_exists($finalDir)) {
mkdir($finalDir, 0777, true);
}
$fileName = $id . '.mp4';
$finalPath = "$finalDir/$fileName";
$files = glob("$chunkPath/*");
sort($files, SORT_NATURAL);
$out = fopen($finalPath, 'ab');
foreach ($files as $file) {
$in = fopen($file, 'rb');
stream_copy_to_stream($in, $out);
fclose($in);
}
fclose($out);
array_map('unlink', $files);
rmdir($chunkPath);
}
public function revert(Request $request)
{
$uuid = $request->getContent();
Storage::disk('public')->deleteDirectory("chunks/$uuid");
return response()->noContent();
}
}
Create a route in routes/web.php.
routes/web.php
<?php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\UploadController;
Route::get('/', function () {
return view('upload');
});
Route::post('/upload', [UploadController::class, 'process']);
Route::patch('/upload/{id}', [UploadController::class, 'uploadChunk']);
Route::delete('/revert', [UploadController::class, 'revert']);
Run the Application Using Artisan
php artisan serve
This will start the Laravel development server, usually accessible at:
http://127.0.0.1:8000
Upload an Image or Video
Uploaded Images or Videos