Works with any backend npm install Zero dependencies

PHP

Handle MultipleUpload file uploads with plain PHP using the $_FILES superglobal and move_uploaded_file. No frameworks required -- works on any PHP hosting environment.

Multipart Form Upload

Save this as upload.php. It receives the file via standard multipart form POST and moves it to the uploads directory.

<?php
// upload.php
header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Methods: POST, OPTIONS");
header("Access-Control-Allow-Headers: Content-Type, X-Upload-Id, X-Chunk-Index, X-Chunk-Count, X-File-Name, X-File-Size");
header("Content-Type: application/json");

if ($_SERVER["REQUEST_METHOD"] === "OPTIONS") {
 http_response_code(204);
 exit;
}

$uploadDir = __DIR__ . "/uploads/";
if (!is_dir($uploadDir)) {
 mkdir($uploadDir, 0755, true);
}

if ($_SERVER["REQUEST_METHOD"] !== "POST" || empty($_FILES["file"])) {
 http_response_code(400);
 echo json_encode(["success" => false, "error" => "No file uploaded"]);
 exit;
}

$file = $_FILES["file"];

if ($file["error"] !== UPLOAD_ERR_OK) {
 http_response_code(400);
 echo json_encode(["success" => false, "error" => "Upload error code: " . $file["error"]]);
 exit;
}

$fileGuid = bin2hex(random_bytes(16));
$fileGuid = sprintf("%s-%s-%s-%s-%s",
 substr($fileGuid, 0, 8),
 substr($fileGuid, 8, 4),
 substr($fileGuid, 12, 4),
 substr($fileGuid, 16, 4),
 substr($fileGuid, 20)
);

$ext = pathinfo($file["name"], PATHINFO_EXTENSION);
$destPath = $uploadDir . $fileGuid . ($ext ? "." . $ext : "");

if (!move_uploaded_file($file["tmp_name"], $destPath)) {
 http_response_code(500);
 echo json_encode(["success" => false, "error" => "Failed to save file"]);
 exit;
}

echo json_encode([
 "success" => true,
 "fileGuid" => $fileGuid,
 "fileName" => $file["name"],
 "fileSize" => $file["size"]
]);

Chunked Upload

Save this as upload-chunk.php. Each chunk arrives as a raw body with custom headers. Chunks are stored in a temporary directory and assembled when the last chunk arrives.

<?php
// upload-chunk.php
header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Methods: POST, OPTIONS");
header("Access-Control-Allow-Headers: Content-Type, X-Upload-Id, X-Chunk-Index, X-Chunk-Count, X-File-Name, X-File-Size");
header("Content-Type: application/json");

if ($_SERVER["REQUEST_METHOD"] === "OPTIONS") {
 http_response_code(204);
 exit;
}

$uploadDir = __DIR__ . "/uploads/";
$chunkDir = __DIR__ . "/uploads/chunks/";

$uploadId = $_SERVER["HTTP_X_UPLOAD_ID"] ?? "";
$chunkIndex = (int)($_SERVER["HTTP_X_CHUNK_INDEX"] ?? 0);
$chunkCount = (int)($_SERVER["HTTP_X_CHUNK_COUNT"] ?? 0);
$fileName = $_SERVER["HTTP_X_FILE_NAME"] ?? "unknown";
$fileSize = (int)($_SERVER["HTTP_X_FILE_SIZE"] ?? 0);

if (!$uploadId || !$chunkCount) {
 http_response_code(400);
 echo json_encode(["success" => false, "error" => "Missing chunk headers"]);
 exit;
}

$sessionDir = $chunkDir . $uploadId . "/";
if (!is_dir($sessionDir)) {
 mkdir($sessionDir, 0755, true);
}

// Save chunk from raw input
$chunkData = file_get_contents("php://input");
file_put_contents($sessionDir . "chunk_" . $chunkIndex, $chunkData);

// Count received chunks
$received = count(glob($sessionDir . "chunk_*"));

if ($received === $chunkCount) {
 // Assemble final file
 $fileGuid = bin2hex(random_bytes(16));
 $fileGuid = sprintf("%s-%s-%s-%s-%s",
 substr($fileGuid, 0, 8),
 substr($fileGuid, 8, 4),
 substr($fileGuid, 12, 4),
 substr($fileGuid, 16, 4),
 substr($fileGuid, 20)
 );

 $ext = pathinfo($fileName, PATHINFO_EXTENSION);
 $finalPath = $uploadDir . $fileGuid . ($ext ? "." . $ext : "");
 $output = fopen($finalPath, "wb");

 for ($i = 0; $i < $chunkCount; $i++) {
 $chunkPath = $sessionDir . "chunk_" . $i;
 $data = file_get_contents($chunkPath);
 fwrite($output, $data);
 unlink($chunkPath);
 }
 fclose($output);
 rmdir($sessionDir);

 echo json_encode([
 "success" => true,
 "fileGuid" => $fileGuid,
 "fileName" => $fileName,
 "fileSize" => $fileSize
 ]);
 exit;
}

echo json_encode(["success" => true, "chunksReceived" => $received]);

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 PHP endpoints.

<link rel="stylesheet" href="multipleupload.css" />
<div id="uploader"></div>
<script src="multipleupload.js"></script>
<script>
 var uploader = new MultipleUpload("#uploader", {
 uploadUrl: "/upload.php",
 chunkUploadUrl: "/upload-chunk.php",
 chunkSize: 2 * 1024 * 1024, // 2 MB chunks
 onFileUploaded: function (file, response) {
 console.log("Uploaded:", response.fileName, response.fileGuid);
 }
 });
</script>