Works with any backend npm install Zero dependencies

Laravel

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.

Routes

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"]);

Multipart Form Upload

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(),
 ]);
 }
}

Chunked Upload

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]);
}

CORS Configuration

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,
];

Expected JSON Response

Both endpoints must return this JSON structure on successful upload completion.

{
 "success": true,
 "fileGuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
 "fileName": "photo.jpg",
 "fileSize": 204800
}

Client-Side JavaScript

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>