gohugoio/hugo · critical

content source: failed to convert %T to string

Error message

content source: failed to convert %T to string: %s

What it means

This is NOT a returned error but a PANIC: Source.ValueAsString calls cast.ToStringE on the content Value and panics with this message if conversion fails. It is triggered when a content source value is a type spf13/cast cannot stringify (e.g. a struct, map, or complex number). Because it panics, an unrecovered occurrence crashes the build.

Solutions

  1. Always set Source.Value to a string, []byte, or an io.ReadSeeker for content, never a nested struct/map.
  2. Marshal complex data to a string (e.g. json.Marshal) before assigning it to Source.Value.
  3. If consuming Source, wrap the call in a recover() guard to convert the panic into a logged error.

Example fix

// before
src := pagemeta.Source{Value: someStruct}
// after
b, _ := json.Marshal(someStruct)
src := pagemeta.Source{Value: string(b)}
Defensive patterns

Strategy: validation

Validate before calling

switch s.Value.(type) {
case nil, string, []byte:
    // safe
cinstance:
    // marshal to string before ValueAsString
}

Type guard

func isStringableContent(v any) bool {
    switch v.(type) {
    case nil, string, []byte, fmt.Stringer:
        return true
    }
    return false
}

Try / catch

defer func() {
    if r := recover(); r != nil {
        err = fmt.Errorf("content value not stringable: %v", r)
    }
}()
str := src.ValueAsString()

Prevention

When it happens

Trigger: A content adapter or Source.Value is set to a non-stringifiable Go value (struct, map[any]any with nested objects, big.Float, etc.) and code later calls ValueAsString/ValueAsOpenReadSeekCloser to read the content.

Common situations: Content adapters passing raw decoded JSON/YAML nested structures straight into Source.Value instead of a string/[]byte/io.Reader. Surfaces as a build crash rather than a graceful error.

Related errors


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

Appendix: source

Thrown at resources/page/pagemeta/page_frontmatter.go:560

	Value any
}

func (s Source) IsZero() bool {
	return !hreflect.IsTruthful(s.Value)
}

func (s Source) IsResourceValue() bool {
	_, ok := s.Value.(resource.Resource)
	return ok
}

func (s Source) ValueAsString() string {
	if s.Value == nil {
		return ""
	}
	ss, err := cast.ToStringE(s.Value)
	if err != nil {
		panic(fmt.Errorf("content source: failed to convert %T to string: %s", s.Value, err))
	}
	return ss
}

func (s Source) ValueAsOpenReadSeekCloser() hugio.OpenReadSeekCloser {
	content := s.ValueAsString()
	return func() (hugio.ReadSeekCloser, error) {
		return hugio.NewReadSeekerNoOpCloserFromString(content), nil
	}
}

// FrontMatterOnlyValues holds values that can only be set via front matter.
type FrontMatterOnlyValues struct {
	ResourcesMeta []map[string]any
}

// FrontMatterHandler maps front matter into Page fields and .Params.
// Note that we currently have only extracted the date logic.

View on GitHub (pinned to 52c9bd7908)