gohugoio/hugo · error
too many YAML aliases for non-scalar nodes
Error message
too many YAML aliases for non-scalar nodes
What it means
Thrown by validateAliasLimitForCollections (parser/metadecoders/decoder.go:231) when a YAML document's non-scalar nodes (maps/lists) are referenced via anchors/aliases more than a computed limit. This is Hugo's defense against the 'billion laughs' / 'delayed laughter' exponential-expansion attack: a small malicious YAML can reference collection anchors enough times to blow up memory when later rendered. The limit scales with file size (100 for <2KB, 5000 for <10KB, 10000 otherwise).
Solutions
- Reduce the number of collection aliases in the YAML by inlining repeated structures or splitting the file.
- If the data is legitimately large and trusted, convert it to JSON (no anchors) to bypass the YAML alias validation entirely.
- For very large legitimate shared-row data, consider generating the data programmatically rather than via YAML aliases.
- Audit the file for accidental anchor loops introduced by a templating/export tool.
Example fix
# before (over-aliased)
_base: &base { a: 1, b: 2 }
rows:
- *base
- *base
# ... thousands more
# after (inline or use JSON)
rows:
- { a: 1, b: 2 }
- { a: 1, b: 2 } Defensive patterns
Strategy: validation
Validate before calling
// Reject overly-aliased YAML before Hugo loads it (heuristic: count aliases).
func estimateAliasCount(data []byte) int {
return bytes.Count(data, []byte("*"))
} Try / catch
if err := metadecoders.UnmarshalYaml(data, &m); err != nil {
if strings.Contains(err.Error(), "too many YAML aliases") {
// Convert to JSON or inline structures to bypass the limit.
}
return err
} Prevention
- Prefer JSON over YAML for large data files with shared rows.
- Inline repeated structures instead of aliasing.
- Treat untrusted YAML as untrusted; sanitize before loading.
When it happens
Trigger: Loading a YAML front-matter or data file that uses anchors (&base) referenced by many aliases (*base) on maps/lists such that the count exceeds the size-based limit; a legitimately large data file with aggressive alias reuse; a malicious or malformed data file fed into Hugo.
Common situations: A large YAML data file under data/ where every row aliases a shared map (can exceed 1000+ refs); a third-party theme/module shipping an over-aliased config; processing untrusted user-supplied YAML.
Related errors
- cannot unmarshal CSV into %T
- cannot unmarshal CSV into %T: expected at least a header…
- cannot unmarshal CSV into %T: header row contains duplicate…
- cannot unmarshal CSV into %T: invalid targetType: expected…
- failed to unmarshal XML
AI-assisted analysis of gohugoio/hugo@52c9bd7908 (2026-08-09).
Data as JSON: /api/errors/6430f203e5d182be.
Report an issue: GitHub.
Appendix: source
Thrown at parser/metadecoders/decoder.go:231
// we can easily get a delayed laughter when we try to render this very big structure later,
// e.g. via RenderString.
func validateAliasLimitForCollections(v any, limit int) error {
if limit <= 0 {
limit = 1000
}
collectionRefCounts := make(map[uintptr]int)
checkCollectionRef := func(v *any) error {
// Conversion of a Pointer to a uintptr (but not back to Pointer) is considered safe.
// See https://pkg.go.dev/unsafe#pkg-functions
ptr := uintptr(unsafe.Pointer(v))
if ptr == 0 {
return nil
}
collectionRefCounts[ptr]++
if collectionRefCounts[ptr] > limit {
return fmt.Errorf("too many YAML aliases for non-scalar nodes")
}
return nil
}
var validate func(v any) error
validate = func(v any) error {
switch vv := v.(type) {
case *map[string]any:
if err := checkCollectionRef(&v); err != nil {
return err
}
for _, vvv := range *vv {
if err := validate(vvv); err != nil {
return err
}
}
case map[string]any:
if err := checkCollectionRef(&v); err != nil {View on GitHub (pinned to 52c9bd7908)