OrchardCMS/OrchardCore · error · ArgumentException

Content part name must start with a letter

Error message

Content part name must start with a letter

What it means

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.

Solutions

  1. Prefix the name with a letter, e.g. "Part1" instead of "1Part".
  2. Validate the name before calling Named()/AlterPartDefinitionAsync: char.IsLetter(name[0]).
  3. Ensure the name is non-null and non-empty before Build().

Example fix

// before
builder.Named("123Part").Build(); // throws

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

Strategy: validation

Validate before calling

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

Try / catch

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

Prevention

When it happens

Trigger: 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.

Common situations: Auto-generated part names from external data (numbers, GUID prefixes), sanitization stripped the leading letter, empty name from a misconfigured recipe or migration.

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/e7d92e78cf7353dd. Report an issue: GitHub.

Appendix: source

Thrown at src/OrchardCore/OrchardCore.ContentManagement.Abstractions/Metadata/Builders/ContentPartDefinitionBuilder.cs:44

        {
            _fields = [];
            _settings = [];
        }
        else
        {
            Name = existing.Name;
            _fields = existing.Fields.ToList();
            _settings = existing.Settings.Clone();
        }
    }

    public string Name { get; private set; }

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

        return new ContentPartDefinition(Name, _fields, _settings);
    }

    public ContentPartDefinitionBuilder Named(string name)
    {
        Name = name;

        return this;

View on GitHub (pinned to 4306c0717f)