PHPOffice/PhpSpreadsheet · error · PhpOffice\PhpSpreadsheet\Writer\Exception

Registered writers must implement PhpOffice\PhpSpreadsheet\W

Error message

Registered writers must implement PhpOffice\PhpSpreadsheet\Writer\IWriter

What it means

IOFactory::registerWriter() validates the class-string with is_a($writerClass, IWriter::class, true) before storing it in the registry. Registering a class that does not implement PhpOffice\PhpSpreadsheet\Writer\IWriter - including nonexistent class names (is_a returns false) and namespace typos - throws immediately instead of failing later during save.

Source

Thrown at src/PhpSpreadsheet/IOFactory.php:269

            'gnumeric' => 'Gnumeric',
            'htm', 'html' => 'Html',
            // Do nothing
            // We must not try to use CSV reader since it loads
            // all files including Excel files etc.
            'csv' => null,
            default => null,
        };
    }

    /**
     * Register a writer with its type and class name.
     *
     * @param class-string<IWriter> $writerClass
     */
    public static function registerWriter(string $writerType, string $writerClass): void
    {
        if (!is_a($writerClass, IWriter::class, true)) {
            throw new Writer\Exception('Registered writers must implement ' . IWriter::class);
        }

        self::$writers[$writerType] = $writerClass;
    }

    /**
     * Register a reader with its type and class name.
     *
     * @param class-string<IReader> $readerClass
     */
    public static function registerReader(string $readerType, string $readerClass): void
    {
        if (!is_a($readerClass, IReader::class, true)) {
            throw new Reader\Exception('Registered readers must implement ' . IReader::class);
        }

        self::$readers[$readerType] = $readerClass;
    }

View on GitHub (pinned to 65b080eef4)

Solutions

  1. Implement the full IWriter interface (save(), getSpreadsheet(), etc.) on the custom class, or extend an existing writer such as Writer\Pdf\Mpdf
  2. Fix the namespace/class-string and pass the ::class constant, never an instance
  3. When registering dynamic names, check is_a($class, IWriter::class, true) yourself first to give a better error

Example fix

// before
class MyPdfWriter { /* no IWriter */ }
IOFactory::registerWriter('MyPdf', MyPdfWriter::class);

// after
class MyPdfWriter extends \PhpOffice\PhpSpreadsheet\Writer\Pdf\Mpdf { }
IOFactory::registerWriter('MyPdf', MyPdfWriter::class);
Defensive patterns

Strategy: type-guard

Type guard

/** Guard before IOFactory::registerWriter(). */
function isRegisterableWriter(string $class): bool
{
    return class_exists($class)
        && is_a($class, \PhpOffice\PhpSpreadsheet\Writer\IWriter::class, true);
}

if (!isRegisterableWriter($writerClass)) {
    throw new InvalidArgumentException("$writerClass must implement IWriter");
}
IOFactory::registerWriter('MyPdf', $writerClass);

Try / catch

try {
    IOFactory::registerWriter('MyPdf', $configuredWriterClass);
} catch (\PhpOffice\PhpSpreadsheet\Writer\Exception $e) {
    // registration is safe to skip: continue with built-in writers only
    error_log("Skipping writer registration: {$e->getMessage()}");
}

Prevention

When it happens

Trigger: IOFactory::registerWriter('MyPdf', MyPdfWriter::class) where MyPdfWriter does not implement IWriter; passing an object instance instead of the class-string; a class-string with a misspelled namespace.

Common situations: Custom PDF/export wrappers written for the older PHPExcel API; registering user-supplied class names from configuration; copy-pasted registrations after a framework upgrade.

Related errors


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