gohugoio/hugo · error

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

Error message

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

What it means

The parser reached the end of the page content while inside a shortcode whose template uses .Inner/.InnerDeindent (needsInner) but never saw its closing tag or a self-closing '/>'. Because inner-consuming shortcodes delimit a content region, an unterminated one is ambiguous, so Hugo aborts instead of guessing where it ends.

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 8a468df065)

Solutions

  1. Add the matching closing tag {{< /name >}} (or {{% /name %}} for the markup form) at the intended end of the inner content.
  2. If no inner content is wanted, self-close it: {{< name />}}.
  3. Check for delimiter mismatch — the closing tag must use the same delimiter style ({{< >}} vs {{% %}}) as the opener, and nested shortcodes must close in order.

Example fix

<!-- before -->
{{< details summary="More" >}}
Hidden text

<!-- after -->
{{< details summary="More" >}}
Hidden text
{{< /details >}}
Defensive patterns

Strategy: validation

Validate before calling

// Lint: every {{< name >}} whose template uses .Inner must have a matching {{< /name >}}
// or be written self-closed {{< name />}}

Prevention

When it happens

Trigger: Content contains {{< name >}} for a shortcode whose template references .Inner, but the matching {{< /name >}} is missing, misspelled (e.g. {{< /nmae >}}), malformed, or the call wasn't self-closed as {{< name />}}. Nested shortcodes with mismatched closers trigger it too.

Common situations: Forgetting the closing tag after switching a shortcode to use .Inner; truncated content files; mismatched delimiters ({{% name %}} opened but {{< /name >}} used to close); copy-paste dropping the closing line; nesting errors where an inner shortcode consumes the outer's closer.

Related errors


AI-assisted analysis of gohugoio/hugo@8a468df065 (2026-07-31). Data as JSON: /data/errors/d1875dac9d7c1f94.json. Report an issue: GitHub ↗.