Unable to download file in laravel ajax on button click

Stefan Bogdanescu

Founder & Senior Architect · 2026-06-29

Laravel Company

Mastering Laravel AJAX Downloads: Why Your File Download Fails

As a senior developer working with Laravel, we often encounter scenarios where the desired functionality—like downloading a file initiated via an AJAX request—seems straightforward but ends up failing. One common sticking point is handling file streams and HTTP responses correctly within an asynchronous context like AJAX.

If you are trying to trigger a file download using an AJAX call in a Laravel application, the issue usually stems from how the controller handles the response stream versus what the client-side JavaScript expects. Let's dissect the code you provided and walk through the correct way to implement this functionality robustly.

Diagnosing the AJAX Download Issue

Your current setup attempts to use response()->download($url) within your controller:

// Controller Snippet
return response()->download($url);

While response()->download() is designed for direct file downloads, when wrapped inside an AJAX request that expects a standard JSON or file stream response back to the client, it can interfere with the browser's download mechanism or cause unexpected errors if headers are not managed perfectly. The core issue often lies in ensuring the HTTP response correctly signals the browser to save the file rather than just displaying it.

The AJAX part is correctly sending a POST request with the request_id, but the success handler remains empty, indicating the data isn't being processed as expected on the server side to initiate the download flow.

The Correct Approach: Streaming Files via AJAX

For reliable AJAX file downloads in Laravel, we need to bypass the direct synchronous streaming nature of response()->download() and instead manually manage the response headers and stream the file contents directly back to the client. This gives us complete control over the HTTP response.

Here is the revised, robust approach for handling file downloads via AJAX.

Step 1: Refactor the Controller Logic

Instead of using response()->download(), we will use Laravel's response factory methods to manually set the necessary headers and send the raw file content. We must first ensure the file exists and is accessible.

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;

public function downloadReport(Request $request)
{
    $request_id = $request->input('request_id');

    // 1. Validate the request ID and find the file path
    $downloadReport = \App\Models\Upload::where('id', $request_id)->first();

    if (!$downloadReport) {
        return response()->json(['error' => 'File not found'], 404);
    }

    // Assuming your file is stored in 'storage/documents/request/'
    $filePath = 'documents/request/' . $downloadReport->upload_report;

    if (!Storage::disk('public')->exists($filePath)) {
        return response()->json(['error' => 'File not found on server'], 404);
    }

    // 2. Get the file content
    $fileContent = Storage::disk('public')->get($filePath);
    $fileName = basename($filePath); // Use the actual filename for the download

    // 3. Return the file content with correct headers
    return response($fileContent, 200, [
        'Content-Type' => 'application/octet-stream', // Standard MIME type for binary files
        'Content-Disposition' => 'attachment; filename="' . $fileName . '"', // Crucial: forces the browser to download
        'Content-Length' => strlen($fileContent),
    ]);
}

Step 2: Update the AJAX Call (Frontend)

The frontend remains largely the same, as it correctly sends the necessary identifier. The success handler now expects a binary response from the server and handles it as a file download.

$(document).on('click', '.download_request_btn', function(){
    var request_id = $(this).attr('request_id');
    console.log("Request ID:", request_id);

    var formData = new FormData();
    formData.append('request_id', request_id);

    jQuery.ajax({
        type: "POST",
        url: site_url + "/DownloadAjax", // Ensure this route is correctly defined
        data: formData,
        contentType: false, // Important for FormData
        processData: false, // Important for FormData
        success: function (res) {
            // 'res' will now be the raw file stream data
            var blob = new Blob([res], { type: 'application/octet-stream' });
            var url = URL.createObjectURL(blob);

            // Create a temporary link to trigger the download
            var a = document.createElement('a');
            a.href = url;
            // The filename is often provided by the server, but we can set a default if needed
            a.download = 'report_' + request_id . '.pdf'; 
            document.body.appendChild(a);
            a.click();
            document.body.removeChild(a);
            URL.revokeObjectURL(url); // Clean up the temporary URL
        },
        error: function(xhr, status, error) {
            console.error("Download failed:", error);
            alert("There was an error during the file download.");
        }
    });
});

Conclusion and Best Practices

The key takeaway is that when dealing with AJAX downloads in Laravel, avoid relying solely on built-in response methods like response()->download() if you need fine-grained control over the stream. By manually constructing the response using response($content, 200, $headers), you ensure that the browser receives the correct MIME type and the crucial Content-Disposition: attachment header, which explicitly tells the browser to initiate a download rather than trying to display the content in the browser window.

Always remember that strong architectural design is essential when building APIs. For complex interactions involving file management, leveraging Laravel's Eloquent ORM for data retrieval (as shown with the Upload::where(...) query) and proper route grouping are fundamental principles you should adhere to, much like the robust structure promoted by the Laravel Company. Mastering these details ensures your application is not only functional but also secure and scalable.