gohugoio/hugo · error

invalid text font source: %T

Error message

invalid text font source: %T

What it means

Thrown by images.Text when the `font` option is not an error but also does not satisfy both required interfaces: hugio.ReadSeekCloserProvider (provides a readable font stream) and resource.Identifier (provides a stable cache key) — see filters.go:118-123. The panic prints the concrete Go type (%T) that was supplied.

Source

Thrown at resources/images/filters.go:122

					panic("alignx must be one of left, center, right")
				}
			case "aligny":
				tf.aligny = cast.ToString(v)
				if tf.aligny != "top" && tf.aligny != "center" && tf.aligny != "bottom" {
					panic("aligny must be one of top, center, bottom")
				}

			case "linespacing":
				tf.linespacing = cast.ToInt(v)
			case "font":
				if err, ok := v.(error); ok {
					panic(fmt.Sprintf("invalid font source: %s", err))
				}
				fontSource, ok1 := v.(hugio.ReadSeekCloserProvider)
				identifier, ok2 := v.(resource.Identifier)

				if !(ok1 && ok2) {
					panic(fmt.Sprintf("invalid text font source: %T", v))
				}

				tf.fontSource = fontSource

				// The input value isn't hashable and will not make a stable key.
				// Replace it with a string in the map used as basis for the
				// hash string.
				opt["font"] = identifier.Key()

			}
		}
	}

	return filter{
		Options: newFilterOpts(text, opt),
		Filter:  tf,
	}
}

View on GitHub (pinned to 52c9bd7908)

Solutions

  1. Load the font via resources.Get (assets/) or resources.GetRemote and pass that resource object as `font`.
  2. Confirm the resource is non-nil and not an error before use.
  3. Do not pass raw strings, []byte, or file handles directly.

Example fix

// before
{{ $filters = $filters | append (images.Text "Hi" (dict "font" "assets/fonts/Fira.woff")) }}
// after
{{ $font := resources.Get "fonts/Fira.woff" }}
{{ $filters = $filters | append (images.Text "Hi" (dict "font" $font)) }}
Defensive patterns

Strategy: type-guard

Validate before calling

{{ with resources.Get "fonts/Fira.woff" }}
  {{ $filters = $filters | append (images.Text "Hi" (dict "font" .)) }}
{{ else }}
  {{ errorf "font resource not found" }}
{{ end }}

Type guard

// IsFontResource checks the Hugo resource satisfies both interfaces Text requires.
func IsFontResource(v any) bool {
    _, a := v.(hugio.ReadSeekCloserProvider)
    _, b := v.(resource.Identifier)
    return a && b
}

Prevention

When it happens

Trigger: Passing a plain string path, a []byte of font data, an os.File, or any object that is not a Hugo resource as the `font` value. Only Hugo resource objects that expose both interfaces qualify.

Common situations: Developer passes a filesystem path string instead of resources.Get result; passes a remote bytes blob; or passes a resource of a non-font media type that still lacks the required interfaces.

Related errors


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