Chunked file uploads with Laravel & AJAX

Stefan Bogdanescu

Founder & Senior Architect · 2026-06-29

Laravel Company

Mastering Large File Transfers: Implementing Chunked Uploads with Laravel and AJAX

Dealing with large file uploads via a single HTTP request often leads to frustrating failures due to server memory limits (memory_limit) or timeout issues. When users attempt to upload files larger than a few hundred megabytes, the standard method breaks down. As senior developers, we need robust solutions. This is where Chunked File Uploads become essential, providing a resilient and scalable way to handle massive data transfers in a web application built with Laravel.

This post will guide you through the architecture and implementation of chunked file uploads using AJAX, allowing you to upload files in manageable pieces without overloading your server or client memory.

Why Chunked Uploads are Necessary

The fundamental problem with large file uploads is that they consume significant RAM on both the client side (holding the entire file buffer) and the server side (attempting to read the entire file into memory before processing).

Chunked uploading solves this by:

  1. Reducing Memory Footprint: The file is broken into smaller, manageable chunks.
  2. Improving Resilience: If an upload fails mid-way, only the failed chunk needs to be resent, not the entire massive file.
  3. Faster Feedback: Progress updates can be sent back to the user immediately after each successful chunk transfer via AJAX.

The Architecture: Frontend, Backend, and Orchestration

Implementing this requires a three-part strategy: client-side splitting, server-side reception, and final assembly.

1. Frontend: Splitting the File with JavaScript

The browser handles the initial file reading using the File.slice() method to divide the file into chunks of a predefined size (e.g., 5MB). Each chunk is then sent individually via AJAX.

Here is how you would modify your JavaScript logic to handle chunking instead of a single submission:

$(document).ready(function(){
    // Configuration
    const chunkSize = 5 * 1024 * 1024; // 5MB chunk size
    const fileInput = $('#file-7')[0];
    const file = fileInput.files[0];
    let currentChunk = 0;

    // Function to handle sending a single chunk
    function uploadChunk(chunk) {
        $.ajax({
            url: "{{ route('upload_chunk') }}", // New route needed for chunks
            method: "POST",
            data: new FormData({ chunk_data: chunk, file_name: file.name, total_chunks: 10 }), // Send the chunk data
            contentType: false,
            processData: false,
            success: function(response) {
                // Handle success for this specific chunk (e.g., update progress bar)
                console.log("Chunk uploaded successfully:", response);
                currentChunk++;
                if (currentChunk < 10) {
                    uploadChunk(file.slice(currentChunk * chunkSize, (currentChunk + 1) * chunkSize));
                } else {
                    completeUpload(); // Final step
                }
            },
            error: function(xhr, status, error) {
                console.error("Error uploading chunk:", error);
                // Implement robust error handling here
            }
        });
    }

    $('#upload_form').on('submit', function(event){
        event.preventDefault();
        if (!file) return;

        // Start the process by sending the first chunk
        uploadChunk(file.slice(0, chunkSize));
    });
});

2. Backend: Receiving and Storing Chunks in Laravel

The server needs a new route (e.g., /upload_chunk) to handle these smaller requests. Instead of trying to store the whole file at once, you will temporarily store each chunk received. For complex data like this, storing file parts in temporary storage or a database is often necessary before final assembly.

In your Laravel Controller, you would process the incoming chunk:

use Illuminate\Support\Facades\Storage;

protected function uploadChunk(Request $request)
{
    $chunkData = $request->file('chunk_data'); // The actual binary data of the chunk
    $fileName = $request->input('file_name');
    $chunkNumber = $request->input('chunk_number');

    // Store the chunk temporarily. We store it based on the original file name and chunk index.
    $path = Storage::disk('local')->put("temp_uploads/{$fileName}_{$chunkNumber}", $chunkData);

    return response()->json([
        'status' => 'chunk_received',
        'chunk' => $chunkNumber,
        'path' => $path
    ]);
}

3. Finalization: Reassembling the File

Once all chunks have been successfully uploaded and stored on the server (perhaps in a temporary location), a final request is sent to the server. The backend then uses these temporary paths to read all the pieces sequentially, concatenate them, and save the final, complete file. This process ensures that memory limits are never breached during the actual transfer phase, leveraging Laravel's robust storage capabilities.

Conclusion

Chunked file uploads transform a potentially failing monolithic operation into a series of reliable, manageable transactions. By adopting this pattern—using smart JavaScript for splitting and Laravel for segmented storage—you gain the flexibility required to handle arbitrarily large files without worrying about server memory constraints. For scalable file management in your Laravel application, leveraging these techniques ensures a superior user experience and robust backend performance. If you are looking for deep dives into efficient data handling within the Laravel ecosystem, exploring official documentation resources on https://laravelcompany.com will provide excellent architectural insights.