{"id":"846e5852bbbd0f81","repo":"gohugoio/hugo","slug":"invalid-path-q","errorCode":null,"errorMessage":"invalid path %q","messagePattern":"invalid path %q","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"hugolib/pagesfromdata/pagesfromgotmpl.go","lineNumber":74,"sourceCode":"\t// The return value will always be an empty string.\n\tEnableAllLanguages() string\n}\n\nvar _ PagesFromDataTemplateContext = (*pagesFromDataTemplateContext)(nil)\n\ntype pagesFromDataTemplateContext struct {\n\tp *PagesFromTemplate\n}\n\nfunc (p *pagesFromDataTemplateContext) toPathSitesMap(v any) (string, map[string]any, map[string]any, error) {\n\tm, err := hmaps.ToStringMapE(v)\n\tif err != nil {\n\t\treturn \"\", nil, nil, err\n\t}\n\n\tpath, err := cast.ToStringE(m[\"path\"])\n\tif err != nil {\n\t\treturn \"\", nil, nil, fmt.Errorf(\"invalid path %q\", path)\n\t}\n\n\tsites := hmaps.ToStringMap(m[\"sites\"])\n\n\treturn path, sites, m, nil\n}\n\nfunc (p *pagesFromDataTemplateContext) AddPage(v any) (string, error) {\n\tpath, sites, m, err := p.toPathSitesMap(v)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\thash, hasChanged := p.p.buildState.checkHasChangedAndSetSourceInfo(path, sites, m)\n\tif !hasChanged {\n\t\treturn \"\", nil\n\t}\n","sourceCodeStart":56,"sourceCodeEnd":92,"githubUrl":"https://github.com/gohugoio/hugo/blob/8a468df065a75c1c7cf9f6850f32148746590ea5/hugolib/pagesfromdata/pagesfromgotmpl.go#L56-L92","documentation":"Returned by toPathSitesMap in hugolib/pagesfromdata/pagesfromgotmpl.go:74 when the map passed to a content adapter's AddPage (from a _content.gotmpl template) has a 'path' value that cannot be cast to a string. Every page created from a content adapter must have a string path, so Hugo rejects the entry immediately. Note the message interpolates the (empty) cast result, so it typically reads invalid path \"\".","triggerScenarios":"Calling $.AddPage in _content.gotmpl with a dict whose 'path' key is a non-string-castable value (a slice, map, or nil object from remote JSON), or where cast.ToStringE fails on the value type.","commonSituations":"Content adapters built over remote APIs where the expected field is missing or nested (passing the whole object instead of .path); building the path with a template pipeline that yields a slice; typo'd key so 'path' is absent and a non-castable zero value flows in; migrating data whose id field is numeric-array or object typed.","solutions":["Ensure the dict passed to .AddPage has a 'path' key whose value is a plain string (e.g. \"path\" (printf \"posts/%s\" .id)).","Print the offending item with warnf/debug in the template to see the actual type of .path.","Coerce values explicitly in the template with the string func before adding the page.","Validate the remote data shape (transform.Unmarshal result) before iterating."],"exampleFix":"{{/* before: path is a map */}}\n{{ $.AddPage (dict \"path\" .slugObject \"title\" .title) }}\n\n{{/* after: path is a string */}}\n{{ $.AddPage (dict \"path\" (string .slugObject.slug) \"title\" .title) }}","handlingStrategy":"validation","validationCode":"// Validate paths passed to AddPage in content adapters (_content.gotmpl)\n{{ $path := \"posts/my-post\" }}\n{{ if or (strings.HasPrefix $path \"/\") (eq $path \"\") (in $path \"..\") }}\n  {{ errorf \"bad page path %q\" $path }}\n{{ end }}\n{{ $.AddPage (dict \"path\" $path \"title\" \"My Post\") }}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["In `_content.gotmpl`, always pass relative, clean paths (no leading slash, no `..`, non-empty) to AddPage/AddResource","Sanitize externally sourced slugs (e.g. from an API) with `path.Clean` / `urlize` before using them as page paths","Add a test site fixture in CI exercising the content adapter with representative data"],"tags":["hugo","content-adapter","templates","type-error"],"analyzedSha":"8a468df065a75c1c7cf9f6850f32148746590ea5","analyzedAt":"2026-07-31T21:23:07.045Z","schemaVersion":2}