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

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.

Solutions

  1. Rename the part so it starts with a letter (prefix with a word, e.g. 'ThreeDCardPart')
  2. If the legacy name must be preserved for data, keep the storage name and use a letter-prefixed display name
  3. Fix the generating code/migration to sanitize part names with ToSafeName and a letter prefix

Example fix

// before
builder.AlterTypeDefinitionAsync("Product", t => t.WithPart("2FAPart", "2FAPart"));
// after
builder.AlterTypeDefinitionAsync("Product", t => t.WithPart("TwoFAPart", "TwoFAPart"));
Defensive patterns

Strategy: validation

Validate before calling

if (string.IsNullOrEmpty(partName) || !char.IsLetter(partName[0]))
    throw new ArgumentException($"Part name '{partName}' must start with a letter.");

Try / catch

try
{
    await builder.AlterTypeDefinitionAsync(typeName, t => t.WithPart(partName));
}
catch (ArgumentException ex) when (ex.Message.Contains("must start with a letter"))
{
    _logger.LogError(ex, "Invalid part name '{Name}'", partName);
}

Prevention

When it happens

Trigger: Attaching a content part whose name starts with a digit or non-letter (e.g. '2FAPart', '_Meta') via WithPart / Attach during AlterTypeDefinitionAsync

Common situations: Programmatically generating part names from numeric codes or versions ('3DCardPart'); migration scripts mapping legacy part names that started with digits or underscores

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

Appendix: source

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

        return this;
    }

    private sealed class PartConfigurerImpl : ContentTypePartDefinitionBuilder
    {
        private readonly ContentPartDefinition _partDefinition;

        public PartConfigurerImpl(ContentTypePartDefinition part)
            : base(part)
        {
            Current = part;
            _partDefinition = part.PartDefinition;
        }

        public override ContentTypePartDefinition Build()
        {
            if (!char.IsLetter(Current.Name[0]))
            {
                throw new ArgumentException("Content part name must start with a letter", "name");
            }

            if (!string.Equals(Current.Name, Current.Name.ToSafeName(), StringComparison.OrdinalIgnoreCase))
            {
                throw new ArgumentException("Content part name contains invalid characters", "name");
            }

            return new ContentTypePartDefinition(Current.Name, _partDefinition, _settings)
            {
                ContentTypeDefinition = Current.ContentTypeDefinition,
            };
        }
    }
}

View on GitHub (pinned to 4306c0717f)