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

  1. Clear the Hugo build cache and the _hugoautogen workspace directory, then rebuild: rm -rf _hugoautogen and run hugo --gc.
  2. 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.
  3. 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.
  4. 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

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


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 nil

View on GitHub (pinned to 52c9bd7908)