Handle MultipleUpload file uploads with Laravel using the built-in
Request file handling and
storage facade. This guide covers both standard multipart uploads and chunked uploads.
Register the upload routes in routes/api.php.
// routes/api.php
use App\Http\Controllers\UploadController;
Route::post("/upload", [UploadController::class, "upload"]);
Route::post("/upload-chunk", [UploadController::class, "uploadChunk"]);
The controller receives the file through Laravel's Request object and stores it with a unique GUID.
<?php
// app/Http/Controllers/UploadController.php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\Storage;
class UploadController extends Controller
{
public function upload(Request $request)
{
$request->validate([
"file" => "required|file|max:204800" // 200 MB
]);
$file = $request->file("file");
$fileGuid = (string) Str::uuid();
$ext = $file->getClientOriginalExtension();
$storedName = $fileGuid . ($ext ? ".{$ext}" : "");
$file->storeAs("uploads", $storedName, "local");
return response()->json([
"success" => true,
"fileGuid" => $fileGuid,
"fileName" => $file->getClientOriginalName(),
"fileSize" => $file->getSize(),
]);
}
}
Each chunk arrives as a raw body with custom headers. The controller stores chunks in a temporary directory and assembles them when the final chunk arrives.
// Add to UploadController
public function uploadChunk(Request $request)
{
$uploadId = $request->header("X-Upload-Id");
$chunkIndex = (int) $request->header("X-Chunk-Index");
$chunkCount = (int) $request->header("X-Chunk-Count");
$fileName = $request->header("X-File-Name");
$fileSize = (int) $request->header("X-File-Size");
if (!$uploadId || !$chunkCount) {
return response()->json(["success" => false, "error" => "Missing headers"], 400);
}
$chunkDir = "chunks/{$uploadId}";
$chunkData = $request->getContent();
Storage::disk("local")->put("{$chunkDir}/chunk_{$chunkIndex}", $chunkData);
// Count received chunks
$received = count(Storage::disk("local")->files($chunkDir));
if ($received === $chunkCount) {
// Assemble final file
$fileGuid = (string) Str::uuid();
$ext = pathinfo($fileName, PATHINFO_EXTENSION);
$finalName = $fileGuid . ($ext ? ".{$ext}" : "");
$finalPath = storage_path("app/uploads/{$finalName}");
$output = fopen($finalPath, "wb");
for ($i = 0; $i < $chunkCount; $i++) {
$data = Storage::disk("local")->get("{$chunkDir}/chunk_{$i}");
fwrite($output, $data);
}
fclose($output);
// Clean up chunks
Storage::disk("local")->deleteDirectory($chunkDir);
return response()->json([
"success" => true,
"fileGuid" => $fileGuid,
"fileName" => $fileName,
"fileSize" => $fileSize,
]);
}
return response()->json(["success" => true, "chunksReceived" => $received]);
}
Update config/cors.php to allow the custom chunk headers.
// config/cors.php
return [
"paths" => ["api/*"],
"allowed_origins" => ["*"],
"allowed_methods" => ["POST", "OPTIONS"],
"allowed_headers" => [
"Content-Type", "X-Upload-Id", "X-Chunk-Index",
"X-Chunk-Count", "X-File-Name", "X-File-Size"
],
"max_age" => 86400,
];
Both endpoints must return this JSON structure on successful upload completion.
{
"success": true,
"fileGuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"fileName": "photo.jpg",
"fileSize": 204800
}
Initialize MultipleUpload on the client, pointing to your Laravel API endpoints.
<link rel="stylesheet" href="multipleupload.css" />
<div id="uploader"></div>
<script src="multipleupload.js"></script>
<script>
var uploader = new MultipleUpload("#uploader", {
uploadUrl: "/api/upload",
chunkUploadUrl: "/api/upload-chunk",
chunkSize: 2 * 1024 * 1024, // 2 MB chunks
onFileUploaded: function (file, response) {
console.log("Uploaded:", response.fileName, response.fileGuid);
}
});
</script>