gohugoio/hugo · error
npm pack: failed to marshal JSON
Error message
npm pack: failed to marshal JSON: %w
What it means
Thrown by the writeJSON helper inside Hugo's npm package builder (modules/npm/package_builder.go:368) when encoding/json.Encoder.Encode returns an error while serializing the auto-generated package.json or package metadata. The %w wraps the underlying json error, which Go produces when the value contains a type that cannot be marshaled (chan, func, complex) or a self-referential structure. In Hugo this value is constructed internally from module dependency maps, so it typically indicates corrupt or unexpectedly-typed module dependency data rather than user-authored JSON.
Solutions
- Clear the Hugo build cache and the _hugoautogen workspace directory, then rebuild: rm -rf _hugoautogen and run hugo --gc.
- Inspect the module declarations in hugo.toml/hugo.yaml for malformed dependencies and remove or fix any non-string values in the replacements/mounts/imports.
- Check the error chain (%w) printed by Hugo to identify which field/type failed to marshal, then report a Hugo issue if the value originates from Hugo internals rather than your config.
- Pin to a known-good Hugo version if the regression appeared after an upgrade.
Example fix
// before (malformed module dep with non-string value)
[module]
[[module.imports]]
path = "github.com/x/y"
ignoreConfig = ["a"] // array where scalar expected by resolver
// after
[module]
[[module.imports]]
path = "github.com/x/y"
ignoreConfig = "a" Defensive patterns
Strategy: try-catch
Validate before calling
// Validate module dependency maps contain only JSON-serializable values before build.
func depsAreJSONSerializable(m map[string]any) error {
for k, v := range m {
if err := json.Marshal(v); err != nil {
return fmt.Errorf("dependency %q not serializable: %w", k, err)
}
}
return nil
} Type guard
// Reject chan/func/complex which json.Encoder cannot marshal.
func isJSONSerializable(v any) bool {
rv := reflect.ValueOf(v)
switch rv.Kind() {
case reflect.Chan, reflect.Func, reflect.Complex64, reflect.Complex128:
return false
}
return true
} Try / catch
err := writeJSON(fs, file, pkg)
if err != nil {
// Surface the wrapped json error, clean _hugoautogen, and abort the npm pack step.
log.Printf("npm pack json marshal failed: %v", err)
return fmt.Errorf("cannot write %s: %w", file, err)
} Prevention
- Keep module dependency declarations as plain string versions/paths.
- Clear _hugoautogen between builds when iterating on module config.
- Pin Hugo versions in CI to detect regressions in dependency resolution.
When it happens
Trigger: Hugo invokes writeJSON with the autoGenPkg map (name, version, dependencies, devDependencies) at line 214 and the packageMeta struct at line 231. The error fires only if Encoder.Encode(v) returns non-nil — i.e., one of the dependency values inside moduleDeps/moduleDevDeps cannot be JSON-serialized (e.g. a func, chan, or a map with a non-string key) or a struct field is unexported/incompatible.
Common situations: A Hugo module declares a dependency whose resolved metadata contains an unsupported Go type (rare, usually a Hugo internal bug); a third-party module patches the dependency map with a non-serializable value; upgrading Hugo changes the module dependency structure; the workspace package auto-generation (_hugoautogen) hits inconsistent state after a failed prior build leaving stale artifacts.
Related errors
- could not determine content directory for
- failed to clone
- failed to decode module download result
- failed to decode modules list
- failed to map git repo for module
AI-assisted analysis of gohugoio/hugo@52c9bd7908 (2026-08-09).
Data as JSON: /api/errors/af9ca3678dff6dda.
Report an issue: GitHub.
Appendix: source
Thrown at modules/npm/package_builder.go:368
}
func detectIndent(data []byte) string {
for line := range bytes.SplitSeq(data, []byte("\n")) {
trimmed := bytes.TrimLeft(line, " \t")
if len(trimmed) < len(line) && len(trimmed) > 0 && trimmed[0] == '"' {
return string(line[:len(line)-len(trimmed)])
}
}
return " "
}
func writeJSON(fs afero.Fs, filename string, v any) error {
buf := new(bytes.Buffer)
encoder := json.NewEncoder(buf)
encoder.SetEscapeHTML(false)
encoder.SetIndent("", strings.Repeat(" ", 2))
if err := encoder.Encode(v); err != nil {
return fmt.Errorf("npm pack: failed to marshal JSON: %w", err)
}
return afero.WriteFile(fs, filename, buf.Bytes(), 0o666)
}
func toStringSlice(v any) []string {
if v == nil {
return nil
}
if s, ok := v.([]any); ok {
var out []string
for _, item := range s {
if str, ok := item.(string); ok {
out = append(out, str)
}
}
return out
}
return nilView on GitHub (pinned to 52c9bd7908)