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
- Append a version to the engine name, e.g. "node18", "chrome90", "safari15".
- If you don't care about a specific engine, use an ES version instead: "es2018" or "esnext".
- 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
- Pass plain semver-ish versions to js.Build target engines (e.g. "chrome100"), no "v" prefix or ranges
- Validate build options coming from site params before invoking js.Build
- Pin the option in config rather than computing it dynamically
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
- inject: absolute paths not supported, must be relative to /a
- unsupported target: %v
- invalid loader: %q
- unsupported script output format: %q
- inject: file %q not found
AI-assisted analysis of gohugoio/hugo@8a468df065 (2026-07-31).
Data as JSON: /data/errors/5fefb45cd4dd3988.json.
Report an issue: GitHub ↗.