OrchardCMS/OrchardCore · error · ArgumentException

Content type name must start with a letter

Error message

Content type name must start with a letter

What it means

ContentTypeDefinitionBuilder.Build throws ArgumentException when the content type name's first character is not a letter. Type names are used as safe identifiers across indexes, routing and display binding.

Solutions

  1. Prefix the type name with a letter, e.g. "Product2024" instead of "2024Product".
  2. Validate before building: char.IsLetter(name[0]).
  3. Ensure the name is non-null and non-empty before Build().

Example fix

// before
builder.Named("2FACode").Build(); // throws

// after
builder.Named("Code2FA").Build();
Defensive patterns

Strategy: validation

Validate before calling

bool IsValidTypeName(string name) =>
    !string.IsNullOrEmpty(name) && char.IsLetter(name[0]);

Try / catch

try
{
    var typeDef = builder.Build();
}
catch (ArgumentException e)
{
    _logger.LogError(e, "Invalid content type name '{Name}'", name);
}

Prevention

When it happens

Trigger: builder.Named("1Product") / Named("") then Build(); AlterTypeDefinitionAsync migrations generating type names from data starting with digits; null/empty name.

Common situations: Type names derived from database table or entity names with numeric prefixes, recipes with invalid type names, programmatic type creation from external catalogs.

Understand the failure class

Background: "invalid id" errors: invalid identifier format — why libraries reject IDs before lookup, and how to fix them — this error's family across 37 libraries.

Related errors


AI-assisted analysis of OrchardCMS/OrchardCore@4306c0717f (2026-09-13). Data as JSON: /api/errors/4302c576e0df7f91. Report an issue: GitHub.

Appendix: source

Thrown at src/OrchardCore/OrchardCore.ContentManagement.Abstractions/Metadata/Builders/ContentTypeDefinitionBuilder.cs:43

        if (existing == null)
        {
            _parts = [];
            _settings = [];
        }
        else
        {
            _name = existing.Name;
            _displayName = existing.DisplayName;
            _parts = existing.Parts.ToList();
            _settings = existing.Settings.Clone();
        }
    }

    public ContentTypeDefinition Build()
    {
        if (!char.IsLetter(_name[0]))
        {
            throw new ArgumentException("Content type name must start with a letter", "name");
        }
        if (!string.Equals(_name, _name.ToSafeName(), StringComparison.OrdinalIgnoreCase))
        {
            throw new ArgumentException("Content type name contains invalid characters", "name");
        }
        if (_name.IsReservedContentName())
        {
            throw new ArgumentException("Content type name is reserved for internal use", "name");
        }

        return new ContentTypeDefinition(_name, _displayName, _parts, _settings);
    }

    [Obsolete("This method has been deprecated, please use WithName() instead.")]
    public ContentTypeDefinitionBuilder Named(string name) => WithName(name);

    public ContentTypeDefinitionBuilder WithName(string name)
    {

View on GitHub (pinned to 4306c0717f)