gohugoio/hugo · error

: shortcode must be closed or self-closed

Error message

%s: shortcode %q must be closed or self-closed

What it means

A shortcode that expects inner content (its template evaluates .Inner or .InnerDeindent, i.e. needsInner() is true) was opened but never closed, nor was it self-closed. Hugo requires such shortcodes to appear as a matched pair {{< name >}}...{{< /name >}} or as a self-closing tag {{< name />}}.

Solutions

  1. Add the matching closing tag {{< /shortcodeName >}} after the inner content.
  2. If the shortcode should not wrap content, use the self-closing form {{< shortcodeName />}}.
  3. Verify the shortcode template truly needs inner content; if it does not use .Inner, the opening/closing pairing is unnecessary.
  4. Search the reported content file for the shortcode name to locate the unbalanced tag.

Example fix

// before
{{< highlight go >}}
func main() {}

// after
{{< highlight go >}}
func main() {}
{{< /highlight >}}
Defensive patterns

Strategy: validation

Validate before calling

// Lint content for unclosed wrapping shortcodes by pairing open/close tokens.
func validateShortcodeBalance(tokens []scToken) error {
    var stack []string
    for _, t := range tokens {
        switch {
        case t.isOpen():
            if !t.isSelfClosing() { stack = append(stack, t.name) }
        case t.isClose():
            if len(stack) == 0 || stack[len(stack)-1] != t.name {
                return fmt.Errorf("unbalanced closing tag %q", t.name)
            }
            stack = stack[:len(stack)-1]
        }
    }
    if len(stack) > 0 {
        return fmt.Errorf("shortcode %q must be closed or self-closed", stack[len(stack)-1])
    }
    return nil
}

Prevention

When it happens

Trigger: Writing {{< highlight go >}} without the corresponding {{< /highlight >}}, or forgetting the trailing slash on a self-closing form. The check at shortcode.go:710 fires when parsing reaches the IsDone state while closed==false and sc.needsInner()==true.

Common situations: Closing a code block with ``` but forgetting {{< /highlight >}}; renaming a shortcode and leaving an orphan opening tag; content pasted from a Markdown source that stripped the closing tag.

Related errors


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

Appendix: source

Thrown at hugolib/shortcode.go:711

			} else {
				// positional params
				if sc.params == nil {
					var params []any
					params = append(params, currItem.ValTyped(source))
					sc.params = params
				} else {
					if params, ok := sc.params.([]any); ok {
						params = append(params, currItem.ValTyped(source))
						sc.params = params
					} else {
						return sc, fmt.Errorf("%s: invalid state: invalid param type %T for shortcode %q, expected a slice", errorPrefix, params, sc.name)
					}
				}
			}
		case currItem.IsDone():
			if !currItem.IsError() {
				if !closed && sc.needsInner() {
					return sc, fmt.Errorf("%s: shortcode %q must be closed or self-closed", errorPrefix, sc.name)
				}
			}
			// handled by caller
			pt.Backup()
			break Loop

		}
	}
	return sc, nil
}

// Replace prefixed shortcode tokens with the real content.
// Note: This function will rewrite the input slice.
func expandShortcodeTokens(
	ctx context.Context,
	source []byte,
	tokenHandler func(ctx context.Context, token string) ([]byte, error),
) ([]byte, error) {

View on GitHub (pinned to 52c9bd7908)