gohugoio/hugo · error
invalid module path %q; must be relative to themesDir when d
Error message
invalid module path %q; must be relative to themesDir when defined outside of the project
What it means
`createThemeDirname` (modules/client.go:877) maps a non-Go-module import path to a directory under `themesDir`. For imports declared by a module other than the project itself, the resolved path must stay inside `themesDir`: absolute paths and relative paths that escape via `..` are rejected. This is a path-traversal guard — a third-party theme module must not be able to point Hugo at arbitrary filesystem locations.
Source
Thrown at modules/client.go:878
}
if goModOnly {
return nil
}
if err := c.rewriteGoMod(goSumFilename, isGoMod); err != nil {
return err
}
return nil
}
func (c *Client) shouldNotVendor(path string) bool {
return c.noVendor != nil && c.noVendor.Match(path)
}
func (c *Client) createThemeDirname(modulePath string, isProjectMod bool) (string, error) {
invalid := fmt.Errorf("invalid module path %q; must be relative to themesDir when defined outside of the project", modulePath)
modulePath = filepath.Clean(modulePath)
if filepath.IsAbs(modulePath) {
if isProjectMod {
return modulePath, nil
}
return "", invalid
}
moduleDir := filepath.Join(c.ccfg.ThemesDir, modulePath)
if !isProjectMod && !strings.HasPrefix(moduleDir, c.ccfg.ThemesDir) {
return "", invalid
}
return moduleDir, nil
}
// ClientConfig configures the module Client.
type ClientConfig struct {View on GitHub ↗ (pinned to 8a468df065)
Solutions
- In the imported module's config, reference the theme by its module path or by a name relative to themesDir, not an absolute or `../` path.
- If you need a local directory override, declare it in the PROJECT's config via `module.replacements` (or the HUGO_MODULE_REPLACEMENTS env var) — project-level paths are allowed to be absolute.
- Move the local theme into the project's themes directory so a plain name resolves.
- Run `hugo mod graph` to see which module declares the offending import.
Example fix
# before (inside an imported theme's config) [[module.imports]] path = "/home/dev/my-component" # after (in the project's hugo.toml) [module] replacements = "github.com/org/my-component -> /home/dev/my-component"
Defensive patterns
Strategy: validation
Validate before calling
abs, _ := filepath.Abs(modPath)
if !strings.HasPrefix(abs, projectDir) && !strings.HasPrefix(abs, themesDir) {
return fmt.Errorf("module path %q must live under themesDir when outside the project", modPath)
} Prevention
- Keep locally-referenced modules (replace directives, local paths) inside the project or under themesDir
- Use relative paths in module imports rather than absolute paths outside the workspace
- When using `replace` in go.mod for local development, point it inside themesDir
When it happens
Trigger: Module collection where a non-project module (a theme/component you import) declares an import whose path is absolute (e.g. `/home/me/theme`) or resolves outside themesDir after `filepath.Clean` (e.g. `../../other`), and the path was not replaced via a project-level `replace` directive.
Common situations: Copying a theme's dev config that used absolute local paths into a published module; using `../sibling-theme` relative imports inside a nested module; expecting `module.replacements` semantics but declaring the local path directly in the imported module's config instead of the project's.
Related errors
- ErrNotExist
- go command failed: %s
- module %q not found
- %q not found
- failed to apply mounts for project: %w
AI-assisted analysis of gohugoio/hugo@8a468df065 (2026-07-31).
Data as JSON: /data/errors/a9934fa8f04208ae.json.
Report an issue: GitHub ↗.