{"record":{"id":"ef7dc559a2018d53","repo":"gohugoio/hugo","slug":"version-name-q-is-invalid-s","errorCode":null,"errorMessage":"version name %q is invalid: %s","messagePattern":"version name %q is invalid: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"hugolib/versions/versions.go","lineNumber":168,"sourceCode":"\tif r.versionConfigs == nil {\n\t\tr.versionConfigs = make(map[string]VersionConfig)\n\t}\n\tdefaultContentVersionProvided := defaultContentVersion != \"\"\n\tif len(r.versionConfigs) == 0 {\n\t\tif defaultContentVersion == \"\" {\n\t\t\tdefaultContentVersion = defaultContentVersionFallback\n\t\t}\n\t\t// Add a default version.\n\t\tr.versionConfigs[defaultContentVersion] = VersionConfig{}\n\t}\n\n\tvar defaultSeen bool\n\tfor k, v := range r.versionConfigs {\n\t\tif k == \"\" {\n\t\t\treturn errors.New(\"version name cannot be empty\")\n\t\t}\n\t\tif err := paths.ValidateIdentifier(k); err != nil {\n\t\t\treturn fmt.Errorf(\"version name %q is invalid: %s\", k, err)\n\t\t}\n\n\t\tvar isDefault bool\n\t\tif k == defaultContentVersion {\n\t\t\tisDefault = true\n\t\t\tdefaultSeen = true\n\t\t}\n\n\t\t// Ignore error, this may not be a semver version.\n\t\tparsedVersion, _ := version.ParseVersion(k)\n\n\t\tr.Sorted = append(r.Sorted, VersionInternal{Name: k, parsedVersion: parsedVersion, Default: isDefault, VersionConfig: v})\n\t}\n\n\t// Sort by weight if set, then semver descending.\n\tsort.SliceStable(r.Sorted, func(i, j int) bool {\n\t\tri, rj := r.Sorted[i], r.Sorted[j]\n\t\tif ri.Weight == rj.Weight {","sourceCodeStart":150,"sourceCodeEnd":186,"githubUrl":"https://github.com/gohugoio/hugo/blob/52c9bd7908b4d02d4d0ff8f82a888834d6ee10d2/hugolib/versions/versions.go#L150-L186","documentation":"During versions config initialization, each version name key is validated with paths.ValidateIdentifier (versions.go:167-168), which requires the name to equal its normalized form — effectively all lowercase, no spaces, and no characters Hugo's path normalization would alter. A version name that fails (e.g. uppercase letters, spaces, or special characters) produces this error with the underlying reason.","triggerScenarios":"Defining a [versions] config block with keys like 'V1.0', 'My Version', or 'v 1.0' — any key that is not already lowercase-alphanumeric-friendly triggers ValidateIdentifier failure ('must be all lower case and no spaces').","commonSituations":"Using SemVer-looking but uppercase version tags; copying version names from release notes that include spaces; renaming a version in config without normalizing.","solutions":["Lowercase the version name and remove spaces (e.g. 'V1.0' -> 'v1.0', 'My Version' -> 'myversion').","Use dots/dashes which ValidateIdentifier permits: 'v1.0.0', 'v1-0-0'.","Keep version keys stable across content that references them (aliases/redirects) after renaming."],"exampleFix":"// before (config)\n[versions]\n\"V1.2.0\"\n\n// after\n[versions]\n\"v1.2.0\"","handlingStrategy":"validation","validationCode":"func normalizeVersionName(name string) (string, error) {\n    if name == \"\" {\n        return \"\", errors.New(\"version name cannot be empty\")\n    }\n    norm := strings.ToLower(strings.ReplaceAll(name, \" \", \"\"))\n    if err := paths.ValidateIdentifier(norm); err != nil {\n        return \"\", fmt.Errorf(\"version name %q is invalid: %w\", name, err)\n    }\n    return norm, nil\n}","typeGuard":"func isValidVersionName(name string) bool {\n    return name != \"\" && name == paths.NormalizePathStringBasic(name)\n}","tryCatchPattern":null,"preventionTips":["Use lowercase, space-free version keys (dots/dashes allowed).","Add a config lint step that runs ValidateIdentifier on version keys.","Keep version keys stable to avoid breaking content references."],"tags":["versions","config","validation","hugo"],"backgroundTag":null,"analyzedSha":"52c9bd7908b4d02d4d0ff8f82a888834d6ee10d2","analyzedAt":"2026-08-09T21:49:36.660Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}