{"record":{"id":"5faad535a8eee429","repo":"OrchardCMS/OrchardCore","slug":"content-part-name-must-start-with-a-letter-5faad5","errorCode":null,"errorMessage":"Content part name must start with a letter","messagePattern":"Content part name must start with a letter","errorType":"exception","errorClass":"ArgumentException","httpStatus":null,"severity":"error","filePath":"src/OrchardCore/OrchardCore.ContentManagement.Abstractions/Metadata/Builders/ContentTypeDefinitionBuilder.cs","lineNumber":214,"sourceCode":"        return this;\n    }\n\n    private sealed class PartConfigurerImpl : ContentTypePartDefinitionBuilder\n    {\n        private readonly ContentPartDefinition _partDefinition;\n\n        public PartConfigurerImpl(ContentTypePartDefinition part)\n            : base(part)\n        {\n            Current = part;\n            _partDefinition = part.PartDefinition;\n        }\n\n        public override ContentTypePartDefinition Build()\n        {\n            if (!char.IsLetter(Current.Name[0]))\n            {\n                throw new ArgumentException(\"Content part name must start with a letter\", \"name\");\n            }\n\n            if (!string.Equals(Current.Name, Current.Name.ToSafeName(), StringComparison.OrdinalIgnoreCase))\n            {\n                throw new ArgumentException(\"Content part name contains invalid characters\", \"name\");\n            }\n\n            return new ContentTypePartDefinition(Current.Name, _partDefinition, _settings)\n            {\n                ContentTypeDefinition = Current.ContentTypeDefinition,\n            };\n        }\n    }\n}\n","sourceCodeStart":196,"sourceCodeEnd":229,"githubUrl":"https://github.com/OrchardCMS/OrchardCore/blob/4306c0717fe573f6fca1b4955909ddab6a192807/src/OrchardCore/OrchardCore.ContentManagement.Abstractions/Metadata/Builders/ContentTypeDefinitionBuilder.cs#L196-L229","documentation":"ContentTypeDefinitionBuilder's inner ContentTypePartDefinitionBuilder.Build() validates the attached part name must begin with a letter. This ensures the part name can be used safely in HTML field ids, route segments, and storage column names.","triggerScenarios":"Attaching a content part whose name starts with a digit or non-letter (e.g. '2FAPart', '_Meta') via WithPart / Attach during AlterTypeDefinitionAsync","commonSituations":"Programmatically generating part names from numeric codes or versions ('3DCardPart'); migration scripts mapping legacy part names that started with digits or underscores","solutions":["Rename the part so it starts with a letter (prefix with a word, e.g. 'ThreeDCardPart')","If the legacy name must be preserved for data, keep the storage name and use a letter-prefixed display name","Fix the generating code/migration to sanitize part names with ToSafeName and a letter prefix"],"exampleFix":"// before\nbuilder.AlterTypeDefinitionAsync(\"Product\", t => t.WithPart(\"2FAPart\", \"2FAPart\"));\n// after\nbuilder.AlterTypeDefinitionAsync(\"Product\", t => t.WithPart(\"TwoFAPart\", \"TwoFAPart\"));","handlingStrategy":"validation","validationCode":"if (string.IsNullOrEmpty(partName) || !char.IsLetter(partName[0]))\n    throw new ArgumentException($\"Part name '{partName}' must start with a letter.\");","typeGuard":null,"tryCatchPattern":"try\n{\n    await builder.AlterTypeDefinitionAsync(typeName, t => t.WithPart(partName));\n}\ncatch (ArgumentException ex) when (ex.Message.Contains(\"must start with a letter\"))\n{\n    _logger.LogError(ex, \"Invalid part name '{Name}'\", partName);\n}","preventionTips":["Prefix numeric/underscore-leading legacy names with a word ('ThreeDPart' not '3DPart')","Sanitize programmatically generated part names before WithPart","Review migration scripts for parts renamed from systems allowing non-letter-leading identifiers"],"tags":["content-management","validation","naming","content-part"],"backgroundTag":"invalid-identifier-format","analyzedSha":"4306c0717fe573f6fca1b4955909ddab6a192807","analyzedAt":"2026-09-13T17:41:05.024Z","contentChangedAt":"2026-09-13T17:41:05.024Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}