Saving a PhpSpreadSheet through button click

Stefan Bogdanescu

Founder & Senior Architect · 2026-06-29

Laravel Company

Saving a PhpSpreadSheet through Button Click: The Laravel Solution

It is incredibly common for developers working with file generation libraries like PhpSpreadsheet in a framework like Laravel to hit roadblocks when trying to trigger a download via a simple button click. Your experience—trying client-side JavaScript (Axios) and server-side actions that result in empty pages—points directly to the fundamental difference between generating data on the server and serving a file to the client.

As a senior developer, I can assure you that this is not a failure of your concept; it’s a failure of execution regarding how HTTP responses are handled. We need to shift the focus from trying to force the browser to save data directly, to instructing the server (Laravel) to deliver the file correctly.

Here is the comprehensive, developer-focused guide on how to successfully generate and download a PhpSpreadsheet file when a user clicks a button in a Laravel application.


Why Direct Client-Side Saving Fails

When you try to use JavaScript (like Axios) to trigger a file download after an API call, you are typically dealing with the data returned by the server (JSON). While modern JavaScript can initiate downloads, it cannot magically access or stream large binary files generated on the backend directly unless the backend explicitly formats the response as a downloadable stream.

Attempting to attach the button to a standard Laravel action that returns nothing results in an empty page because the request executed successfully but failed to return any meaningful content for the browser to render. The solution lies entirely within the server-side controller logic.

The Server-Side Streaming Solution with PhpSpreadsheet

The correct approach is to let Laravel handle the file generation and then use specific HTTP response mechanisms to tell the browser, "This data is a file, not an HTML page." We will leverage PHP's ability to stream content directly into the HTTP response.

Step 1: Generate the Spreadsheet Data

First, ensure you have successfully loaded and populated your spreadsheet using PhpSpreadsheet. This code runs entirely on the server.

php
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;

// Inside your Controller method...
$spreadsheet = new Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();
$sheet->setCellValue('A1', 'Report Data');
$sheet->setCellValue('B1', 'Value 1');

// ... populate data here ...

Step 2: Stream the File Response

Instead of returning a standard JSON response, we will use Laravel's response helpers to stream the binary content directly to the client with the correct file headers. This is the most robust method for file downloads in Laravel applications.

In your Controller method, you would modify the response logic like this:

php
use Illuminate\Support\Facades\Response;
// ... other necessary imports

public function downloadSpreadsheet()
{
    // 1. Generate the Spreadsheet object (as above)
    $spreadsheet = new \PhpOffice\PhpSpreadsheet\Spreadsheet();
    $sheet = $spreadsheet->getActiveSheet();
    $sheet->setCellValue('A1', 'Report Data');

    // 2. Prepare the file for download
    $writer = new Xlsx($spreadsheet);
    
    // Use the Response facade to stream the file directly
    return Response::stream(function () use ($writer) {
        // Set the appropriate headers for an XLSX file download
        header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
        header('Content-Disposition: attachment;filename="report.xlsx"');
        header('Cache-Control: max-age=0');
        header('Expires:0');

        // Write the file content stream
        $writer->save('php://output');
    });
}

Step 3: Linking the Button to the Action

Finally, your Blade view links a simple form submission or an anchor tag to this new route. Since the action itself triggers a direct file download rather than rendering a page, everything works seamlessly.

html
<!-- In your Blade file -->
<form method="POST" action="{{ route('download.spreadsheet') }}">
    @csrf
    <button type="submit">Download Excel Report</button>
</form>

Conclusion: Embracing Server-Side Responsibility

The key takeaway here is understanding the boundaries of web development. Client-side JavaScript excels at interactivity and manipulating the Document Object Model (DOM); server-side languages like PHP (within Laravel) are designed for heavy lifting, data processing, and managing file system operations.

When dealing with file generation and large binary outputs, always delegate the creation and streaming of that file to the server. By using Laravel's response capabilities to stream the PhpSpreadsheet output directly, you ensure security, efficiency, and a reliable user experience. This principle—using the right tool for the job—is central to building scalable applications on platforms like Laravel. Keep building amazing things with Laravel!