{"record":{"id":"ae584f36bc2802db","repo":"gohugoio/hugo","slug":"role-name-q-is-invalid-s","errorCode":null,"errorMessage":"role name %q is invalid: %s","messagePattern":"role name %q is invalid: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"hugolib/roles/roles.go","lineNumber":154,"sourceCode":"\t\tr.roleConfigs = make(map[string]RoleConfig)\n\t}\n\tdefaultContentRoleProvided := defaultContentRole != \"\"\n\tif len(r.roleConfigs) == 0 {\n\t\t// Add a default role.\n\t\tif defaultContentRole == \"\" {\n\t\t\tdefaultContentRole = defaultContentRoleFallback\n\t\t}\n\t\tr.roleConfigs[defaultContentRole] = RoleConfig{}\n\t}\n\n\tvar defaultSeen bool\n\tfor k, v := range r.roleConfigs {\n\t\tif k == \"\" {\n\t\t\treturn \"\", errors.New(\"role name cannot be empty\")\n\t\t}\n\n\t\tif err := paths.ValidateIdentifier(k); err != nil {\n\t\t\treturn \"\", fmt.Errorf(\"role name %q is invalid: %s\", k, err)\n\t\t}\n\n\t\tvar isDefault bool\n\t\tif k == defaultContentRole {\n\t\t\tisDefault = true\n\t\t\tdefaultSeen = true\n\t\t}\n\n\t\tr.Sorted = append(r.Sorted, RoleInternal{Name: k, Default: isDefault, RoleConfig: v})\n\t}\n\n\t// Sort by weight if set, then by name.\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 {\n\t\t\treturn ri.Name < rj.Name\n\t\t}\n\t\tif rj.Weight == 0 {","sourceCodeStart":136,"sourceCodeEnd":172,"githubUrl":"https://github.com/gohugoio/hugo/blob/52c9bd7908b4d02d4d0ff8f82a888834d6ee10d2/hugolib/roles/roles.go#L136-L172","documentation":"Raised by RolesInternal.init (roles.go:154) when a configured role name fails paths.ValidateIdentifier, which requires the string to equal its NormalizePathStringBasic form — i.e. all lowercase ASCII, no spaces, no slashes, no punctuation outside the basic path character set.","triggerScenarios":"Declaring a role under `roles` in config with uppercase letters, spaces, or special characters (e.g. `Editor Role`, `Editor`, `en-US`). The validator runs on every role-config key during init.","commonSituations":"Defining content roles for a multi-role Hugo setup using friendly display names instead of slugs; copy-pasting role names from a UI label field; locale-style names with hyphens or uppercase region codes.","solutions":["Use lowercase, hyphen-separated, space-free role identifiers in config (e.g. `editor`, `guest`, `en-us`).","If you need a display name, store it separately and keep the key as a slug."],"exampleFix":"# before (hugo.toml)\n[roles]\n  [roles.\"Editor Role\"]\n    weight = 1\n\n# after\n[roles]\n  [roles.editor]\n    weight = 1","handlingStrategy":"validation","validationCode":"# Validate every role key is lowercase, no spaces.\nawk '/^\\[roles\\./ { \n  name=$0; gsub(/\\[roles\\.|\\]/,\"\",name); \n  if (name != tolower(name) || name ~ / /) print \"bad role: \" name\n}' hugo.toml","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Use lowercase, hyphen-only role keys (slugs).","Treat role keys as machine identifiers; store display names separately.","Add the awk check above to CI for hugo.toml."],"tags":["roles","configuration","validation"],"backgroundTag":null,"analyzedSha":"52c9bd7908b4d02d4d0ff8f82a888834d6ee10d2","analyzedAt":"2026-08-09T21:49:36.660Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}