{"record":{"id":"8b141cdff2fc30d6","repo":"OrchardCMS/OrchardCore","slug":"content-type-name-must-start-with-a-letter-8b141c","errorCode":null,"errorMessage":"Content type name must start with a letter","messagePattern":"Content type name must start with a letter","errorType":"exception","errorClass":"ArgumentException","httpStatus":null,"severity":"error","filePath":"src/OrchardCore.Modules/OrchardCore.ContentTypes/Services/ContentDefinitionService.cs","lineNumber":64,"sourceCode":"        S = stringLocalizer;\n    }\n\n    public async Task<ContentTypeDefinition> AddTypeAsync(string name, string displayName)\n    {\n        if (string.IsNullOrWhiteSpace(displayName))\n        {\n            throw new ArgumentException($\"The '{nameof(displayName)}' can't be null or empty.\", nameof(displayName));\n        }\n\n        if (string.IsNullOrWhiteSpace(name))\n        {\n            name = await GenerateContentTypeNameFromDisplayNameAsync(displayName);\n        }\n        else\n        {\n            if (!char.IsAsciiLetter(name[0]))\n            {\n                throw new ArgumentException(\"Content type name must start with a letter\", nameof(name));\n            }\n            if (!string.Equals(name, name.ToSafeName(), StringComparison.OrdinalIgnoreCase))\n            {\n                throw new ArgumentException(\"Content type name contains invalid characters\", nameof(name));\n            }\n        }\n\n        while (await _contentDefinitionManager.LoadTypeDefinitionAsync(name) is not null)\n        {\n            name = VersionName(name);\n        }\n\n        var contentTypeDefinition = new ContentTypeDefinition(name, displayName);\n\n        await _contentDefinitionManager.StoreTypeDefinitionAsync(contentTypeDefinition);\n\n        // Ensure it has its own part.\n        await _contentDefinitionManager.AlterTypeDefinitionAsync(name, builder => builder.WithPart(name));","sourceCodeStart":46,"sourceCodeEnd":82,"githubUrl":"https://github.com/OrchardCMS/OrchardCore/blob/4306c0717fe573f6fca1b4955909ddab6a192807/src/OrchardCore.Modules/OrchardCore.ContentTypes/Services/ContentDefinitionService.cs#L46-L82","documentation":"When an explicit technical name is provided to AddTypeAsync, ContentDefinitionService enforces that it starts with an ASCII letter (so it is a valid .NET/YesSql-safe identifier) and is a safe name. A name whose first character is not an ASCII letter throws ArgumentException. This keeps content type names valid for code generation and display-name classifying.","triggerScenarios":"Calling AddTypeAsync with a technical name beginning with a digit, underscore, or non-ASCII character — e.g. AddTypeAsync(\"2faConfig\", \"2FA Config\") or names from user input without sanitization.","commonSituations":"Importing type definitions from external systems with different naming rules; scripts generating type names from numbers or localized strings; user-supplied names passed straight through without ToSafeName.","solutions":["Prefix the name with a letter (e.g. 'Type2faConfig') before calling the service.","Sanitize user input with name.ToSafeName() and ensure the first character is a letter.","Let the service generate the name by passing null and only supplying displayName.","Validate with char.IsAsciiLetter(name[0]) in your own form validation before calling."],"exampleFix":"// before\nawait _contentDefinitionService.AddTypeAsync(\"2faConfig\", \"2FA Config\");\n// after\nvar safeName = \"2faConfig\".ToSafeName();\nvar name = char.IsAsciiLetter(safeName[0]) ? safeName : \"C\" + safeName;\nawait _contentDefinitionService.AddTypeAsync(name, \"2FA Config\");","handlingStrategy":"validation","validationCode":"if (string.IsNullOrEmpty(name) || !char.IsAsciiLetter(name[0]))\n    throw new ArgumentException(\"Content type name must start with a letter.\", nameof(name));\nif (!string.Equals(name, name.ToSafeName(), StringComparison.OrdinalIgnoreCase))\n    throw new ArgumentException(\"Content type name contains invalid characters.\", nameof(name));","typeGuard":"static bool IsValidTypeName(string? name) => !string.IsNullOrEmpty(name) && char.IsAsciiLetter(name[0]) && name.Equals(name.ToSafeName(), StringComparison.OrdinalIgnoreCase);","tryCatchPattern":"try { await _service.AddTypeAsync(name, displayName); }\ncatch (ArgumentException ex) when (ex.ParamName == nameof(name))\n{\n    ModelState.AddModelError(nameof(name), \"Type names must start with a letter and contain only safe characters.\");\n    return View(model);\n}","preventionTips":["Sanitize names with ToSafeName() and prefix a letter if needed","Let the service generate names from the display name when unsure","Enforce naming rules in import/provisioning scripts","Reject digit- or symbol-leading names in UI validation"],"tags":["contenttypes","naming","argument-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-15T23:17:13.987Z"}