gohugoio/hugo · error

malformed character constant: %s

Error message

malformed character constant: %s

What it means

The Go template parser rejects malformed rune/character constants. For an itemCharConstant it calls strconv.UnquoteChar and requires the remaining tail to be a closing single quote; any leftover text is a malformed character constant. See node.go:636-643.

Source

Thrown at tpl/internal/go_templates/texttemplate/parse/node.go:642

	IsFloat    bool       // Number has a floating-point value.
	IsComplex  bool       // Number is complex.
	Int64      int64      // The signed integer value.
	Uint64     uint64     // The unsigned integer value.
	Float64    float64    // The floating-point value.
	Complex128 complex128 // The complex value.
	Text       string     // The original textual representation from the input.
}

func (t *Tree) newNumber(pos Pos, text string, typ itemType) (*NumberNode, error) {
	n := &NumberNode{tr: t, NodeType: NodeNumber, Pos: pos, Text: text}
	switch typ {
	case itemCharConstant:
		rune, _, tail, err := strconv.UnquoteChar(text[1:], text[0])
		if err != nil {
			return nil, err
		}
		if tail != "'" {
			return nil, fmt.Errorf("malformed character constant: %s", text)
		}
		n.Int64 = int64(rune)
		n.IsInt = true
		n.Uint64 = uint64(rune)
		n.IsUint = true
		n.Float64 = float64(rune) // odd but those are the rules.
		n.IsFloat = true
		return n, nil
	case itemComplex:
		// fmt.Sscan can parse the pair, so let it do the work.
		if _, err := fmt.Sscan(text, &n.Complex128); err != nil {
			return nil, err
		}
		n.IsComplex = true
		n.simplifyComplex()
		return n, nil
	}
	// Imaginary constants can only be complex unless they are zero.

View on GitHub (pinned to 52c9bd7908)

Solutions

  1. Use a double-quoted string literal instead of a single-quoted char constant for multi-character text.
  2. Ensure single-quoted literals contain exactly one valid rune with correct escape syntax.
  3. Fix or remove invalid Unicode escape sequences in the char constant.

Example fix

// before
{{ 'ab' }}

// after
{{ "ab" }}
Defensive patterns

Strategy: validation

Validate before calling

// Validate char literals before relying on a generated template.
func validCharLit(s string) bool {
    if len(s) < 3 || s[0] != '\'' || s[len(s)-1] != '\'' { return false }
    _, _, tail, err := strconv.UnquoteChar(s[1:], '\'')
    return err == nil && tail == "'"
}

Try / catch

_, err := template.New("t").Parse(body)
if err != nil {
    // err contains the file:line; surface to the author
    return err
}

Prevention

When it happens

Trigger: A template literal such as {{ 'ab' }} (multi-rune), {{ '\u000G' }} (invalid escape), or a stray character constant with trailing characters.

Common situations: Copy-pasting Go syntax into templates expecting char semantics; editor auto-completion inserting characters; attempting to express a single quote literal incorrectly.

Understand the failure class

Related errors


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