{"record":{"id":"3ce44fa22c40f8f0","repo":"PHPOffice/PhpSpreadsheet","slug":"could-not-open-file-filename-for-writing","errorCode":null,"errorMessage":"Could not open file \"{$filename}\" for writing.","messagePattern":"Could not open file \"(.+?)\" for writing\\.","errorType":"exception","errorClass":"PhpOffice\\PhpSpreadsheet\\Writer\\Exception","httpStatus":null,"severity":"critical","filePath":"src/PhpSpreadsheet/Writer/BaseWriter.php","lineNumber":119,"sourceCode":"    public function openFileHandle($filename): void\n    {\n        if (!is_string($filename)) {\n            $this->fileHandle = $filename;\n            $this->shouldCloseFile = false;\n\n            return;\n        }\n\n        $mode = 'wb';\n        $scheme = parse_url($filename, PHP_URL_SCHEME);\n        if ($scheme === 's3') {\n            // @codeCoverageIgnoreStart\n            $mode = 'w';\n            // @codeCoverageIgnoreEnd\n        }\n        $fileHandle = $filename ? fopen($filename, $mode) : false;\n        if ($fileHandle === false) {\n            throw new Exception('Could not open file \"' . $filename . '\" for writing.');\n        }\n\n        $this->fileHandle = $fileHandle;\n        $this->shouldCloseFile = true;\n    }\n\n    protected function tryClose(): bool\n    {\n        return fclose($this->fileHandle);\n    }\n\n    /**\n     * Close file handle only if we opened it ourselves.\n     */\n    protected function maybeCloseFileHandle(): void\n    {\n        if ($this->shouldCloseFile) {\n            if (!$this->tryClose()) {","sourceCodeStart":101,"sourceCodeEnd":137,"githubUrl":"https://github.com/PHPOffice/PhpSpreadsheet/blob/65b080eef4d9fd11a5796135ab145883e5c3d6a6/src/PhpSpreadsheet/Writer/BaseWriter.php#L101-L137","documentation":"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.","triggerScenarios":"$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.","commonSituations":"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.","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."],"exampleFix":"// before\n$writer->save('/var/www/export/report.xlsx'); // dir missing or not writable\n\n// after\n$dir = '/var/www/export';\nif (!is_dir($dir)) {\n    mkdir($dir, 0775, true);\n}\nif (!is_writable($dir)) {\n    throw new RuntimeException('Export directory not writable: ' . $dir);\n}\n$writer->save($dir . '/report.xlsx');","handlingStrategy":"validation","validationCode":"$path = '/var/www/exports/report.xlsx';\n$dir = dirname($path);\nif (!is_dir($dir)) {\n    mkdir($dir, 0775, true);\n}\nif (!is_writable($dir)) {\n    throw new RuntimeException('Export directory not writable: ' . $dir);\n}\n$writer->save($path);","typeGuard":null,"tryCatchPattern":"use PhpOffice\\PhpSpreadsheet\\Writer\\Exception as WriterException;\n\ntry {\n    $writer->save($path);\n} catch (WriterException $e) {\n    if (str_contains($e->getMessage(), 'Could not open file')) {\n        // permissions, lock, or full disk: log $path, notify the user,\n        // optionally retry against a temp directory\n    } else {\n        throw $e;\n    }\n}","preventionTips":["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."],"tags":["phpspreadsheet","writer","file-io","permissions","save"],"backgroundTag":"file-permission-denied","analyzedSha":"65b080eef4d9fd11a5796135ab145883e5c3d6a6","analyzedAt":"2026-08-17T05:40:41.646Z","schemaVersion":2},"datasetVersion":"2026-08-17T09:17:11.063Z"}