Works with any backend npm install Zero dependencies
← All Backend Guides

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>