PHPOffice/PhpSpreadsheet · critical · PhpOffice\PhpSpreadsheet\Writer\Exception
Could not open file "{$filename}" for writing.
Error message
Could not open file "{$filename}" for writing. What it means
Every writer's save($filename) funnels into BaseWriter::openFileHandle(), which runs fopen($filename, 'wb') (mode 'w' for s3:// URLs) and throws when fopen returns false, meaning the output file could not be created or opened for writing. Root causes are environmental: missing parent directory, insufficient permissions for the PHP process user, the file being locked by another program, a full disk, or an unavailable stream wrapper.
Source
Thrown at src/PhpSpreadsheet/Writer/BaseWriter.php:119
public function openFileHandle($filename): void
{
if (!is_string($filename)) {
$this->fileHandle = $filename;
$this->shouldCloseFile = false;
return;
}
$mode = 'wb';
$scheme = parse_url($filename, PHP_URL_SCHEME);
if ($scheme === 's3') {
// @codeCoverageIgnoreStart
$mode = 'w';
// @codeCoverageIgnoreEnd
}
$fileHandle = $filename ? fopen($filename, $mode) : false;
if ($fileHandle === false) {
throw new Exception('Could not open file "' . $filename . '" for writing.');
}
$this->fileHandle = $fileHandle;
$this->shouldCloseFile = true;
}
protected function tryClose(): bool
{
return fclose($this->fileHandle);
}
/**
* Close file handle only if we opened it ourselves.
*/
protected function maybeCloseFileHandle(): void
{
if ($this->shouldCloseFile) {
if (!$this->tryClose()) {View on GitHub (pinned to 65b080eef4)
Solutions
- Ensure the parent directory exists and is writable by the PHP user: mkdir(dirname($path), 0775, true), then chown/chmod as needed.
- Pre-flight check with is_writable(dirname($path)) (and is_writable($path) for existing files) and fail with a clear message.
- Use absolute paths; write to a temp file under sys_get_temp_dir() and move it into place afterwards.
- Close the file in other applications and check disk space; verify the stream wrapper is enabled for URL targets.
- If unclear, reproduce with a bare fopen($path, 'wb') to confirm the failure is environmental rather than library-specific.
Example fix
// before
$writer->save('/var/www/export/report.xlsx'); // dir missing or not writable
// after
$dir = '/var/www/export';
if (!is_dir($dir)) {
mkdir($dir, 0775, true);
}
if (!is_writable($dir)) {
throw new RuntimeException('Export directory not writable: ' . $dir);
}
$writer->save($dir . '/report.xlsx'); Defensive patterns
Strategy: validation
Validate before calling
$path = '/var/www/exports/report.xlsx';
$dir = dirname($path);
if (!is_dir($dir)) {
mkdir($dir, 0775, true);
}
if (!is_writable($dir)) {
throw new RuntimeException('Export directory not writable: ' . $dir);
}
$writer->save($path); Try / catch
use PhpOffice\PhpSpreadsheet\Writer\Exception as WriterException;
try {
$writer->save($path);
} catch (WriterException $e) {
if (str_contains($e->getMessage(), 'Could not open file')) {
// permissions, lock, or full disk: log $path, notify the user,
// optionally retry against a temp directory
} else {
throw $e;
}
} Prevention
- Make export directories writable by the web-server user as part of deployment, not by hand later.
- Always build absolute output paths from a known base directory.
- Write to temp then move into place for user-facing downloads.
- On Windows, assume Excel may hold a lock; offer a different filename on retry.
When it happens
Trigger: $writer->save('/exports/report.xlsx') when /exports does not exist or is not writable by the web-server user; the target file is currently open in Excel (Windows lock); disk full; an empty filename; an http:// destination with allow_url_fopen disabled.
Common situations: Export directories owned by root while PHP runs as www-data; CLI scripts using relative output paths from a different cwd; Docker volumes mounted read-only; antivirus or spreadsheet applications holding locks on Windows.
Related errors
- Could not close file after writing.
- Can't open file $filename
- Directory does not exist: $cacheDirectory
- Line ending must be \n (Unix) or \r\n (Windows)
- Syntax error: comma expected in function $function, arg #{$n
AI-assisted analysis of PHPOffice/PhpSpreadsheet@65b080eef4 (2026-08-17).
Data as JSON: /api/errors/3ce44fa22c40f8f0.
Report an issue: GitHub.