gohugoio/hugo · error

exif failed

Error message

exif failed: %v

What it means

Returned (not panicked) by Decoder.Decode when the underlying imagemeta.Decode panics during EXIF parsing. The deferred recover() at meta.go:219 catches the panic and converts it to an error with %v formatting (note: %v, not %w — so the original panic value is stringified, not wrappable). This means Hugo degrades gracefully on malformed EXIF rather than crashing the build.

Solutions

  1. Strip and re-write the EXIF metadata using exiftool: 'exiftool -all= -tagsfromfile @ -CreateDate path/to/image.jpg'.
  2. Re-export the image from the original using a reliable tool (ImageMagick, libvips) to produce clean metadata.
  3. If the image is not critical, exclude EXIF processing by setting imaging.exif.includeFields or excludeFields to limit parsing scope.
  4. If you cannot fix the source image, consider setting disableDate and disableLatLong to reduce the EXIF tags Hugo needs to parse.

Example fix

# before: image has corrupt EXIF causing 'exif failed: <panic message>'

# Fix the image's EXIF from the shell:
# exiftool -overwrite_original -all= path/to/image.jpg
# (strips all metadata; re-add what you need)

# Or in hugo.toml, narrow EXIF parsing to avoid the problematic tags:
[imaging.exif]
excludeFields = "GPS|Exif|Exposure[M|P|B]|Contrast|Resolution|Sharp|JPEG|Metering|Sensing|Saturation|ColorSpace|Flash|WhiteBalance"
Defensive patterns

Strategy: try-catch

Validate before calling

// Pre-validate EXIF integrity before Hugo parses it
// From the shell:
// exiftool -verify path/to/image.jpg
// (exit code non-zero indicates corrupt EXIF)

// Or strip all EXIF to avoid the issue entirely:
// exiftool -overwrite_original -all= path/to/image.jpg

Try / catch

// The Decode method already recovers internally and returns an error.
// Handle it gracefully at the call site:
exifInfo, err := decoder.Decode(filename, format, reader)
if err != nil {
    // err will contain 'exif failed: <panic message>'
    log.Printf("warning: skipping EXIF for %s: %v", filename, err)
    exifInfo = &ExifInfo{} // proceed without EXIF data
}

Prevention

When it happens

Trigger: An image file with malformed, truncated, or non-standard EXIF data causes the EXIF parser to panic. The recover block catches it and returns this error to the caller. Common with images edited by tools that write non-compliant EXIF blocks.

Common situations: Photos edited by older mobile apps that write non-standard EXIF; images with manually edited or stripped metadata that left corrupt IFD entries; HEIC files converted to JPEG with a buggy converter; images from scanner software with malformed tags.

Related errors


AI-assisted analysis of gohugoio/hugo@52c9bd7908 (2026-08-09). Data as JSON: /api/errors/e6299e0dfa80378a. Report an issue: GitHub.

Appendix: source

Thrown at resources/images/meta/meta.go:219

	}

	return d, nil
}

var (
	isTimeTag = func(s string) bool {
		return strings.Contains(s, "Time")
	}
	isGPSTag = func(s string) bool {
		return strings.HasPrefix(s, "GPS")
	}
)

// Filename is only used for logging.
func (d *Decoder) Decode(filename string, format imagemeta.ImageFormat, r io.Reader) (ex *ExifInfo, err error) {
	defer func() {
		if r := recover(); r != nil {
			err = fmt.Errorf("exif failed: %v", r)
		}
	}()

	var tagInfos imagemeta.Tags
	handleTag := func(ti imagemeta.TagInfo) error {
		tagInfos.Add(ti)
		return nil
	}

	shouldHandleTag := func(ti imagemeta.TagInfo) bool {
		if ti.Source == imagemeta.EXIF {
			if !d.noDate {
				// We need the time tags to calculate the date.
				if isTimeTag(ti.Tag) {
					return true
				}
			}
			if !d.noLatLong {

View on GitHub (pinned to 52c9bd7908)