{"record":{"id":"e7d92e78cf7353dd","repo":"OrchardCMS/OrchardCore","slug":"content-part-name-must-start-with-a-letter","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/ContentPartDefinitionBuilder.cs","lineNumber":44,"sourceCode":"        {\n            _fields = [];\n            _settings = [];\n        }\n        else\n        {\n            Name = existing.Name;\n            _fields = existing.Fields.ToList();\n            _settings = existing.Settings.Clone();\n        }\n    }\n\n    public string Name { get; private set; }\n\n    public ContentPartDefinition Build()\n    {\n        if (!char.IsLetter(Name[0]))\n        {\n            throw new ArgumentException(\"Content part name must start with a letter\", \"name\");\n        }\n        if (!string.Equals(Name, Name.ToSafeName(), StringComparison.OrdinalIgnoreCase))\n        {\n            throw new ArgumentException(\"Content part name contains invalid characters\", \"name\");\n        }\n        if (Name.IsReservedContentName())\n        {\n            throw new ArgumentException(\"Content part name is reserved for internal use\", \"name\");\n        }\n\n        return new ContentPartDefinition(Name, _fields, _settings);\n    }\n\n    public ContentPartDefinitionBuilder Named(string name)\n    {\n        Name = name;\n\n        return this;","sourceCodeStart":26,"sourceCodeEnd":62,"githubUrl":"https://github.com/OrchardCMS/OrchardCore/blob/4306c0717fe573f6fca1b4955909ddab6a192807/src/OrchardCore/OrchardCore.ContentManagement.Abstractions/Metadata/Builders/ContentPartDefinitionBuilder.cs#L26-L62","documentation":"ContentPartDefinitionBuilder.Build throws ArgumentException when the part name's first character is not a letter. Part names become identifiers in indexes, routes and C#-adjacent tooling, so they must start with a letter.","triggerScenarios":"Calling builder.Named(\"1MyPart\") / Named(\"-Foo\") / Named(\"\") then Build(); migration code calling AlterPartDefinitionAsync with a generated name beginning with a digit or symbol; Name null or empty causing Name[0] to throw.","commonSituations":"Auto-generated part names from external data (numbers, GUID prefixes), sanitization stripped the leading letter, empty name from a misconfigured recipe or migration.","solutions":["Prefix the name with a letter, e.g. \"Part1\" instead of \"1Part\".","Validate the name before calling Named()/AlterPartDefinitionAsync: char.IsLetter(name[0]).","Ensure the name is non-null and non-empty before Build()."],"exampleFix":"// before\nbuilder.Named(\"123Part\").Build(); // throws\n\n// after\nbuilder.Named(\"Part123\").Build();","handlingStrategy":"validation","validationCode":"bool IsValidPartName(string name) =>\n    !string.IsNullOrEmpty(name) && char.IsLetter(name[0]);","typeGuard":null,"tryCatchPattern":"try\n{\n    builder.Named(name);\n    var def = builder.Build();\n}\ncatch (ArgumentException e)\n{\n    _logger.LogError(e, \"Invalid content part name '{Name}'\", name);\n}","preventionTips":["Validate names at user input / import boundaries, before reaching the builder.","Prefix auto-generated names with a letter if the source begins with digits.","Add unit tests for migration-generated part names."],"tags":["naming","content-part","validation"],"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"}