BookStackApp/BookStack · warning · ImageUploadException

errors.image_upload_replace_type

Error message

errors.image_upload_replace_type

What it means

ImageUploadException with 'errors.image_upload_replace_type' is thrown by ImageRepo::updateImageFile when the extension of the replacement uploaded file does not match the extension of the existing image record's stored path. BookStack forbids replacing an image with a different file type (e.g. swapping a .png for a .jpg) to keep stored data consistent.

Source

Thrown at app/Uploads/ImageRepo.php:183

     */
    public function updateImageDetails(Image $image, $updateDetails): Image
    {
        $image->fill($updateDetails);
        $image->updated_by = user()->id;
        $image->save();
        $this->imageResizer->loadGalleryThumbnailsForImage($image, false);

        return $image;
    }

    /**
     * Update the image file of an existing image in the system.
     * @throws ImageUploadException
     */
    public function updateImageFile(Image $image, UploadedFile $file): void
    {
        if (strtolower($file->getClientOriginalExtension()) !== strtolower(pathinfo($image->path, PATHINFO_EXTENSION))) {
            throw new ImageUploadException(trans('errors.image_upload_replace_type'));
        }

        $image->refresh();
        $image->updated_by = user()->id;
        $image->touch();
        $image->save();

        $this->imageService->replaceExistingFromUpload($image->path, $image->type, $file);
        $this->imageResizer->loadGalleryThumbnailsForImage($image, true);
    }

    /**
     * Destroys an Image object along with its revisions, files and thumbnails.
     *
     * @throws Exception
     */
    public function destroyImage(?Image $image = null): void
    {

View on GitHub (pinned to 18f8469a1c)

Solutions

  1. Upload a replacement file with the same extension as the original image
  2. Alternatively delete the existing image and upload the new one as a fresh image
  3. Check $image->path extension first and convert/normalize the new file client-side before replacing

Example fix

// before
$repo->updateImageFile($image, $jpegFile); // image.path is foo.png
// after
if (strtolower($jpegFile->getClientOriginalExtension()) === strtolower(pathinfo($image->path, PATHINFO_EXTENSION))) {
    $repo->updateImageFile($image, $jpegFile);
} else {
    $repo->destroyImage($image);
    $repo->saveNew($jpegFile, 'foo.jpg', $galleryId);
}
Defensive patterns

Strategy: validation

Validate before calling

$existingExt = strtolower(pathinfo($image->path, PATHINFO_EXTENSION));
$newExt = strtolower($file->getClientOriginalExtension());
if ($existingExt !== $newExt) {
    return back()->withErrors(["Replacement must be .$existingExt to match the original image."]);
}

Try / catch

try {
    $imageRepo->updateImageFile($image, $file);
} catch (ImageUploadException $e) {
    return back()->withErrors(['image' => 'File type must match the original image extension.']);
}

Prevention

When it happens

Trigger: Calling updateImageFile($image, $file) where strtolower($file->getClientOriginalExtension()) !== strtolower(pathinfo($image->path, PATHINFO_EXTENSION)) — i.e. uploading a JPEG to replace a PNG record, or vice versa.

Common situations: Users replacing a gallery/cover image via the image manager UI with a different-format file; automated uploads using the API image replace endpoint with a differently-typed file; case mismatch is handled (lowercased) so only true type differences trigger it.

Related errors


AI-assisted analysis of BookStackApp/BookStack@18f8469a1c (2026-09-02). Data as JSON: /api/errors/145f57e22bbdec56. Report an issue: GitHub.