PHPOffice/PhpSpreadsheet · error · PhpOffice\PhpSpreadsheet\Exception

Could not create temporary file

Error message

Could not create temporary file

What it means

File::temporaryFilename() wraps tempnam(sysGetTempDir(), 'phpspreadsheet') and throws when tempnam returns false, i.e. the system temporary directory could not be used (src/PhpSpreadsheet/Shared/File.php:134). Save paths that need scratch space — CSV/PDF writers, large spreadsheet writes — all funnel through it, so a broken temp dir breaks every save.

Source

Thrown at src/PhpSpreadsheet/Shared/File.php:134

        $path = sys_get_temp_dir();
        if (self::$useUploadTempDirectory) {
            //  use upload-directory when defined to allow running on environments having very restricted
            //      open_basedir configs
            if (ini_get('upload_tmp_dir') !== false) {
                if ($temp = ini_get('upload_tmp_dir')) {
                    if (file_exists($temp)) {
                        $path = $temp;
                    }
                }
            }
        }

        return realpath($path) ?: '';
    }

    public static function temporaryFilename(): string
    {
        return tempnam(self::sysGetTempDir(), 'phpspreadsheet') ?: throw new Exception('Could not create temporary file');
    }

    /**
     * Blocks phar:// and similar RCE-bearing wrappers.
     * Note that many protocols, including http and zip, will already
     * return false for is_file.
     * A whitelist of protocols may be added if needed in future.
     * data: is intentionally allowed (see #4823); callers needing strict
     * on-disk-only semantics must validate $filename themselves.
     */
    public static function prohibitWrappers(string $filename): void
    {
        if (
            Preg::IsMatch('~^phar://~i', $filename)
            || (Preg::isMatch('/^([\w.\s\x00-\x1f]+):/', $filename) && !Preg::isMatch('/^([\w.]+):/', $filename))
            || Preg::isMatch('~^[\w.]+://.*phar:~is', $filename)
        ) {
            throw new Exception(

View on GitHub (pinned to 65b080eef4)

Solutions

  1. Point TMPDIR at a writable directory before saving: putenv('TMPDIR=/var/app/tmp') (and ensure it exists with the right ownership), or fix upload_tmp_dir/sys temp config
  2. Free space or inodes on the temp filesystem
  3. Adjust open_basedir to include the temp directory
  4. Run PHP under a user with write access to the temp dir

Example fix

// before
$writer = new \PhpOffice\PhpSpreadsheet\Writer\Xlsx($spreadsheet);
$writer->save('php://output'); // throws: Could not create temporary file

// after
$tmp = '/var/app/tmp';
if (!is_dir($tmp)) { mkdir($tmp, 0770, true); }
putenv('TMPDIR=' . $tmp);
$writer->save('php://output');
Defensive patterns

Strategy: fallback

Validate before calling

use PhpOffice\PhpSpreadsheet\Shared\File;

function ensureTempDirAvailable(): void
{
    $dir = sys_get_temp_dir();
    if (!is_dir($dir) || !is_writable($dir)) {
        $alt = __DIR__ . '/../storage/tmp';
        if (!is_dir($alt)) { mkdir($alt, 0770, true); }
        putenv('TMPDIR=' . $alt); // tempnam() uses this via sysGetTempDir()
    }
}

Prevention

When it happens

Trigger: The temp directory is full (bytes or inodes), missing, or unwritable for the PHP runtime user; open_basedir excludes sys_getTempDir(); containers run with a read-only root filesystem and no writable TMPDIR; upload_tmp_dir misconfigured to a nonexistent path.

Common situations: Kubernetes containers with readOnlyRootFilesystem and no emptyDir mount; shared hosting with restrictive open_basedir; disk exhausted after repeated large exports; CI environments with tiny tmpfs.

Related errors


AI-assisted analysis of PHPOffice/PhpSpreadsheet@65b080eef4 (2026-08-17). Data as JSON: /api/errors/016426e2839191e2. Report an issue: GitHub.