gohugoio/hugo · error

invalid engine version: %q

Error message

invalid engine version: %q

What it means

js.Build's `target` option accepts either ES versions (es2018, esnext, …) or engine targets like chrome58, node12, safari11. When a target string starts with a known engine name, Hugo splits off the version suffix; an empty suffix (just "chrome" or "node") is invalid because esbuild requires an engine version.

Source

Thrown at internal/js/esbuild/options.go:284

func (opts *Options) compile() (err error) {
	var target api.Target
	for _, value := range opts.Target {
		if v, found := nameTarget[value]; found {
			if v > target {
				target = v
			}
		}
	}

	var engines []api.Engine

OUTER:
	for _, value := range opts.Target {
		for _, engine := range engineKeysSorted {
			if strings.HasPrefix(value, engine) {
				version := value[len(engine):]
				if version == "" {
					return fmt.Errorf("invalid engine version: %q", value)
				}
				engines = append(engines, api.Engine{Name: engineName[engine], Version: version})
				continue OUTER
			}
		}
	}

	if target == 0 && len(engines) == 0 && len(opts.Target) > 0 {
		return fmt.Errorf("unsupported target: %v", opts.Target)
	}

	if target == 0 {
		target = api.ESNext
	}

	var loaders map[string]api.Loader
	if opts.IsCSS {
		loaders = make(map[string]api.Loader)

View on GitHub ↗ (pinned to 8a468df065)

Solutions

  1. Append a version to the engine name, e.g. "node18", "chrome90", "safari15".
  2. If you don't care about a specific engine, use an ES version instead: "es2018" or "esnext".
  3. If the version is templated, ensure the variable is non-empty before building the target string.

Example fix

{{/* before */}}
{{ $opts := dict "target" "node" }}
{{/* after */}}
{{ $opts := dict "target" "node18" }}
Defensive patterns

Strategy: validation

Validate before calling

var reEngineVer = regexp.MustCompile(`^\d+(\.\d+){0,2}$`)
if !reEngineVer.MatchString(ver) {
    return fmt.Errorf("bad engine version %q", ver)
}

Prevention

When it happens

Trigger: Passing a target that matches an engine prefix with nothing after it, e.g. `dict "target" "node"` or `"target" (slice "chrome")` — the version substring after the engine name is empty, so decoding options fails.

Common situations: Writing `target = "node"` expecting a generic Node target; copying browserslist-style names without versions; templating the version number and having the variable render empty.

Related errors


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