{"record":{"id":"c10018c3f8fb558e","repo":"hashicorp/terraform","slug":"s-s-name-may-contain-only-lowercase-letters-dig","errorCode":null,"errorMessage":"%s%s: name may contain only lowercase letters, digits and underscores","messagePattern":"(.+?)(.+?): name may contain only lowercase letters, digits and underscores","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/configs/configschema/internal_validate.go","lineNumber":58,"sourceCode":"\t\t}\n\t\tmultiErr = errors.Join(multiErr, attrS.internalValidate(name, prefix))\n\n\t\t// all attributes within a computed block must also be computed\n\t\tif b.Computed && !attrS.Computed {\n\t\t\tmultiErr = errors.Join(multiErr, fmt.Errorf(\"%s%s: all attributes within computed blocks must also be computed\", prefix, name))\n\t\t}\n\t}\n\n\tfor name, blockS := range b.BlockTypes {\n\t\tif blockS == nil {\n\t\t\tmultiErr = errors.Join(multiErr, fmt.Errorf(\"%s%s: block schema is nil\", prefix, name))\n\t\t\tcontinue\n\t\t}\n\n\t\tif _, isAttr := b.Attributes[name]; isAttr {\n\t\t\tmultiErr = errors.Join(multiErr, fmt.Errorf(\"%s%s: name defined as both attribute and child block type\", prefix, name))\n\t\t} else if !validName.MatchString(name) {\n\t\t\tmultiErr = errors.Join(multiErr, fmt.Errorf(\"%s%s: name may contain only lowercase letters, digits and underscores\", prefix, name))\n\t\t}\n\t\tif !blockS.Deprecated && blockS.DeprecationMessage != \"\" {\n\t\t\tmultiErr = errors.Join(multiErr, fmt.Errorf(\"%s%s: DeprecationMessage must not be set when Deprecated is false\", prefix, name))\n\t\t}\n\n\t\tif blockS.MinItems < 0 || blockS.MaxItems < 0 {\n\t\t\tmultiErr = errors.Join(multiErr, fmt.Errorf(\"%s%s: MinItems and MaxItems must both be greater than zero\", prefix, name))\n\t\t}\n\n\t\t// any nested blocks within a computed block must also be computed\n\t\tif b.Computed && !blockS.Computed {\n\t\t\tmultiErr = errors.Join(multiErr, fmt.Errorf(\"%s%s: all nested blocks within computed blocks must also be computed\", prefix, name))\n\t\t}\n\n\t\tswitch blockS.Nesting {\n\t\tcase NestingSingle:\n\t\t\tswitch {\n\t\t\tcase blockS.MinItems != blockS.MaxItems:","sourceCodeStart":40,"sourceCodeEnd":76,"githubUrl":"https://github.com/hashicorp/terraform/blob/d32a084675427f5ac3f7d2868578ef8b2c1dc525/internal/configs/configschema/internal_validate.go#L40-L76","documentation":"During schema validation, a nested block type name does not match the required pattern ^[a-z0-9_]+$. Terraform requires block type names to contain only lowercase letters, digits, and underscores — no uppercase, hyphens, dots, or other characters. This ensures names map cleanly to HCL identifiers and provider SDK conventions.","triggerScenarios":"A key in Block.BlockTypes contains characters outside [a-z0-9_]. Triggered when a provider developer names a nested block with uppercase letters (e.g., 'Subnet'), hyphens (e.g., 'sub-net'), dots (e.g., 'sub.net'), or any other non-conforming character.","commonSituations":"Provider developer mirrors an upstream API field name verbatim that contains hyphens or camelCase (common in cloud provider APIs like AWS, GCP). Schema auto-generation from an API spec that doesn't sanitize names. Copy-paste from API documentation. Developer unfamiliar with Terraform naming conventions uses PascalCase or kebab-case.","solutions":["Rename the nested block type to use only lowercase letters, digits, and underscores (e.g., 'SubnetGroup' → 'subnet_group').","If the name must reflect a hyphenated API field, use underscores: 'load-balancer' → 'load_balancer'.","If using terraform-plugin-framework, the schema definitions naturally enforce this; check for string literals that bypass the framework.","Add a unit test calling InternalValidate() to catch naming violations before release."],"exampleFix":"// before — invalid block name with hyphen\nBlockTypes: map[string]*NestedBlock{\n    \"sub-net\": {Nesting: ListNesting, ...},  // ← hyphen not allowed\n}\n\n// after — snake_case\nBlockTypes: map[string]*NestedBlock{\n    \"sub_net\": {Nesting: ListNesting, ...},\n}","handlingStrategy":"validation","validationCode":"// Provider developers: enforce naming convention on block types\nimport \"regexp\"\n\nvar validBlockName = regexp.MustCompile(`^[a-z0-9_]+$`)\n\nfunc validateBlockTypeNames(b *configschema.Block) error {\n    for name := range b.BlockTypes {\n        if !validBlockName.MatchString(name) {\n            return fmt.Errorf(\"block type name %q must be lowercase letters, digits, and underscores only\", name)\n        }\n    }\n    return b.InternalValidate()\n}\n\n// Unit test gate:\nfunc TestBlockNamesConform(t *testing.T) {\n    if err := myResourceSchema().InternalValidate(); err != nil {\n        t.Fatal(err)\n    }\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Use snake_case for all block type names — never camelCase, PascalCase, kebab-case, or dotted names.","When auto-generating schemas from API specs, sanitize names with a name-normalization function.","Add InternalValidate() to provider test suites to catch naming violations before release."],"tags":["schema","validation","provider-development","naming","naming-convention","internal"],"backgroundTag":null,"analyzedSha":"d32a084675427f5ac3f7d2868578ef8b2c1dc525","analyzedAt":"2026-08-11T18:43:52.779Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}