{"record":{"id":"df0f22abf300c538","repo":"gohugoio/hugo","slug":"invalid-module-config-for-q-both-source-and-targ","errorCode":null,"errorMessage":"invalid module config for %q: both source and target must be set","messagePattern":"invalid module config for %q: both source and target must be set","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"modules/collect.go","lineNumber":800,"sourceCode":"\t\treturn s\n\t}\n\tif strings.HasPrefix(s, \"../../node_modules/\") {\n\t\t// See #14083. This was a common construct to mount node_modules from the project root.\n\t\t// This started failing in v0.152.0 when we tightened the validation.\n\t\treturn strings.TrimPrefix(s, \"../../\")\n\t}\n\treturn \"\"\n}\n\nfunc (c *collector) normalizeMounts(owner *moduleAdapter, mounts []Mount) ([]Mount, error) {\n\tvar out []Mount\n\tdir := owner.Dir()\n\n\tfor _, mnt := range mounts {\n\t\terrMsg := fmt.Sprintf(\"invalid module config for %q\", owner.Path())\n\n\t\tif mnt.Source == \"\" || mnt.Target == \"\" {\n\t\t\treturn nil, errors.New(errMsg + \": both source and target must be set\")\n\t\t}\n\n\t\t// Special case for node_modules imports in themes/modules.\n\t\t// See #14089.\n\t\tvar isModuleNodeModulesImport bool\n\t\tif !owner.projectMod {\n\t\t\tnodeModulesImportSource := c.nodeModulesRoot(mnt.Source)\n\t\t\tif nodeModulesImportSource != \"\" {\n\t\t\t\tisModuleNodeModulesImport = true\n\t\t\t\tmnt.Source = nodeModulesImportSource\n\t\t\t}\n\t\t}\n\n\t\tmnt.Source = filepath.Clean(mnt.Source)\n\t\tmnt.Target = filepath.Clean(mnt.Target)\n\t\tvar sourceDir string\n\n\t\tif !owner.projectMod && !filepath.IsLocal(mnt.Source) {","sourceCodeStart":782,"sourceCodeEnd":818,"githubUrl":"https://github.com/gohugoio/hugo/blob/52c9bd7908b4d02d4d0ff8f82a888834d6ee10d2/modules/collect.go#L782-L818","documentation":"Returned by collector.normalizeMounts in modules/collect.go:800 when a module mount has an empty Source or Target. Mounts map a source path into Hugo's virtual filesystem; both ends are mandatory. The error names the offending module (owner.Path()).","triggerScenarios":"Declaring [[module.mounts]] with source or target omitted/blank, in either the project config or a theme/module's config. normalizeMounts runs during module collection for every mount of every active module.","commonSituations":"A typo'd [[module.mounts]] table missing a key, a config merge that dropped a field, or a theme upgrade whose mounts schema expects a field the project didn't supply.","solutions":["Find the mount flagged by the module name in the error and add the missing source and target.","Ensure source is a non-empty path relative to the module dir and target is a Hugo filesystem dir (e.g. static, content, layouts, assets, archetypes, i18n, data).","Run `hugo config` to print resolved mounts and spot the blank entry."],"exampleFix":"// before\n[[module.mounts]]\nsource = \"content\"\n// after\n[[module.mounts]]\nsource = \"content\"\ntarget = \"content\"","handlingStrategy":"validation","validationCode":"// Validate every mount has both fields before collection.\nfor _, m := range mounts {\n    if m.Source == \"\" || m.Target == \"\" { return errors.New(\"mount missing source/target\") }\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always set both source and target on [[module.mounts]].","Validate mounts via `hugo config` after edits.","Treat a missing field as a schema error in config generation."],"tags":["modules","config","mounts","validation"],"backgroundTag":null,"analyzedSha":"52c9bd7908b4d02d4d0ff8f82a888834d6ee10d2","analyzedAt":"2026-08-09T21:49:36.660Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}