gohugoio/hugo · error
id must not contain backslashes
Error message
id must not contain backslashes
What it means
ValidateBatchID forbids backslashes in a batch id (batch.go:157-158) because backslashes conflict with path handling and virtual import resolution on Windows and produce ambiguous keys. Any id containing '\' is rejected.
Solutions
- Build ids with forward slashes only (use path.Join, not filepath.Join, for virtual ids).
- Sanitize existing strings: replace '\' with '/' before use.
- Prefer Hugo's paths helpers which normalize separators.
Example fix
// before (Windows)
id := filepath.Join("assets", "main") // "assets\main"
// after
id := path.Join("assets", "main") // "assets/main" Defensive patterns
Strategy: validation
Validate before calling
func sanitizeBatchID(id string) (string, error) {
if strings.Contains(id, "\\") {
return "", fmt.Errorf("id must not contain backslashes")
}
return id, nil
}
// Or normalize: strings.ReplaceAll(id, "\\", "/") Type guard
func hasNoBackslashes(id string) bool { return !strings.Contains(id, "\\") } Prevention
- Use path.Join (forward slashes) for virtual ids, not filepath.Join.
- Sanitize Windows-derived strings before passing as ids.
- Prefer Hugo's paths helpers for consistent normalization.
When it happens
Trigger: Passing a Windows-style path (e.g. "assets\\scripts\\main") as a batch id; a string built with backslash separators instead of forward slashes.
Common situations: Code running on Windows that joins paths with filepath.Join (backslash separator) and passes the result as a batch id; copy-pasting a Windows path literal.
Related errors
- id must not contain forward slashes
- failed to build JS batch
- failed to build JS batch
- id must be set
- only esm format is currently supported
AI-assisted analysis of gohugoio/hugo@52c9bd7908 (2026-08-09).
Data as JSON: /api/errors/da5518274d8d085e.
Report an issue: GitHub.
Appendix: source
Thrown at internal/js/esbuild/batch.go:158
func (o *optsHolder[C]) SetOptions(m map[string]any) string {
o.optsSetCounter++
o.optsPrev = o.optsCurr
o.optsCurr = m
o.compiledPrev = o.compiled
o.compiled, o.compileErr = o.compiled.compileOptions(m, o.defaults)
o.checkCompileErr()
return ""
}
// ValidateBatchID validates the given ID according to some very
func ValidateBatchID(id string, isTopLevel bool) error {
if id == "" {
return fmt.Errorf("id must be set")
}
// No Windows slashes.
if strings.Contains(id, "\\") {
return fmt.Errorf("id must not contain backslashes")
}
// Allow forward slashes in top level IDs only.
if !isTopLevel && strings.Contains(id, "/") {
return fmt.Errorf("id must not contain forward slashes")
}
return nil
}
func newIsBuiltOrTouched() isBuiltOrTouched {
return isBuiltOrTouched{
built: make(buildIDs),
touched: make(buildIDs),
}
}
func newOpts[K any, C optionsCompiler[C]](key K, optionsID string, defaults defaultOptionValues) *opts[K, C] {View on GitHub (pinned to 52c9bd7908)