When using the maennchen/zipstream-php
library in a Laravel application, you may encounter the following error:
Cannot modify header information - headers already sent by (output started at .../ZipStream.php:808)
This happens because ZipStream writes directly to the output buffer and sends headers as it streams the ZIP content. If Laravel attempts to send its own response headers afterward, the result is a conflict that PHP raises as an error.
Laravel typically buffers output and sends headers just before the response is returned. However, ZipStream bypasses Laravelās response lifecycle and begins output immediatelyābefore Laravel has had a chance to send its headers. As a result, Laravelās later call to header()
fails because output has already been sent.
To avoid this issue, wrap your ZipStream logic in a StreamedResponse
. This tells Laravel to hand off control of the response entirely, including headers and output.
use ZipStream\ZipStream;
use Symfony\Component\HttpFoundation\StreamedResponse;
public function downloadDocuments()
{
return new StreamedResponse(function () {
$options = new \ZipStream\Option\Archive();
$options->setSendHttpHeaders(true); // let ZipStream send headers
$zip = new ZipStream(null, $options);
$zip->addFile('example.txt', 'This is the content of the file.');
$zip->finish();
});
}
Key takeaways
- ZipStream outputs content and headers directlyābefore Laravel can send its own.
- Wrapping it in
StreamedResponse
avoids Laravel interfering with the output buffer. - Never return a standard
response()
orResponse
object when using ZipStream.
If you stick to StreamedResponse
, Laravel will not try to send any additional headers, and the error will be resolved.
If this post was enjoyable or useful for you, please share it! If you have comments, questions, or feedback, you can email my personal email. To get new posts, subscribe use the RSS feed.