Intervention/image · error · ImageDecoderException

Result must be instance of {ImageInterface::class}

Error message

Result must be instance of {ImageInterface::class}

What it means

The image decoder chain completed but the object it produced is not an ImageInterface. The driver validates the handler's result before returning it. Built-in decoders always return images, so this is a contract violation of a custom decoder plugged into the chain.

Source

Thrown at src/Drivers/AbstractDriver.php:70

     * @throws DriverException
     */
    public function decodeImage(mixed $input, ?array $decoders = null): ImageInterface
    {
        $decoders = $decoders === null ? InputHandler::IMAGE_DECODERS : $decoders;

        if (count($decoders) === 0) {
            throw new InvalidArgumentException('No decoders in array');
        }

        try {
            $result = InputHandler::usingDecoders($decoders, $this)->handle($input);
        } catch (NotSupportedException) {
            $type = is_object($input) ? $input::class : gettype($input);
            throw new InvalidArgumentException('Unsupported image source type "' . $type . '"');
        }

        if (!$result instanceof ImageInterface) {
            throw new ImageDecoderException('Result must be instance of ' . ImageInterface::class);
        }

        return $result;
    }

    /**
     * {@inheritdoc}
     *
     * @see DriverInterface::decodeColor()
     *
     * @throws InvalidArgumentException
     * @throws ColorDecoderException
     * @throws DriverException
     */
    public function decodeColor(mixed $input, ?array $decoders = null): ColorInterface
    {
        $decoders = $decoders === null ? InputHandler::COLOR_DECODERS : $decoders;

View on GitHub (pinned to 5598b9e397)

Solutions

  1. Make the custom decoder return an ImageInterface - typically by wrapping another decoder's result or building the image through the driver
  2. Delegate the heavy lifting to an existing decoder (e.g. BinaryDecoder) and post-process the returned image

Example fix

// before
class S3Decoder implements DecoderInterface
{
    public function decode(mixed $input): mixed
    {
        return $this->s3->get($input); // raw bytes
    }
}

// after
public function decode(mixed $input): ImageInterface
{
    return $this->driver->decodeImage($this->s3->get($input));
}
Defensive patterns

Strategy: type-guard

Type guard

function isValidDecoder(mixed $decoder): bool
{
    return $decoder instanceof \Intervention\Image\Interfaces\DecoderInterface;
}

Try / catch

try {
    $image = $driver->decodeImage($input, [new MyDecoder()]);
} catch (ImageDecoderException $e) {
    // custom decoder broke the contract - fix its return type
}

Prevention

When it happens

Trigger: A custom DecoderInterface implementation whose decode() returns a string, array, null, or a DTO instead of an ImageInterface, invoked via $driver->decodeImage($input, [new MyDecoder()]).

Common situations: Custom decoders for app-specific sources (S3 streams, database blobs, remote URLs) that return raw data instead of delegating to an existing decoder and returning the parsed image.

Related errors


AI-assisted analysis of Intervention/image@5598b9e397 (2026-08-23). Data as JSON: /api/errors/20c1ac3a1599d080. Report an issue: GitHub.