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
- Strip and re-write the EXIF metadata using exiftool: 'exiftool -all= -tagsfromfile @ -CreateDate path/to/image.jpg'.
- Re-export the image from the original using a reliable tool (ImageMagick, libvips) to produce clean metadata.
- If the image is not critical, exclude EXIF processing by setting imaging.exif.includeFields or excludeFields to limit parsing scope.
- 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
- Run 'exiftool -verify' on images from untrusted sources (user uploads, third-party APIs) before committing.
- Strip non-essential EXIF tags with exiftool to reduce the surface for parser panics.
- Use imaging.exif.excludeFields to limit which tags Hugo parses, reducing exposure to malformed data.
- Note this error uses %v not %w, so errors.Is/errors.Unwrap will not work — match on the message prefix.
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
- metadata decoding failed
- failed to load image config
- invalid metadata source
- imaging.avif.compression must be one of lossy or lossless…
- imaging.avif.encoderSpeed must be between 1 and 10, got
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)