Attributes Reference

June 10, 2026 · View on GitHub

All annotation attributes used to define DataSurface resources, fields, relationships, and behavior.


[CrudResource]

Marks a class as a CRUD resource. Applied to the entity class.

[CrudResource("users",
    ResourceKey = "User",
    Backend = StorageBackend.EfCore,
    KeyProperty = "Id",
    MaxPageSize = 200,
    MaxExpandDepth = 1,
    DefaultSort = "-createdAt",
    EnableList = true,
    EnableGet = true,
    EnableCreate = true,
    EnableUpdate = true,
    EnableDelete = true)]
public class User { }
PropertyTypeDefaultDescription
route (positional)string(required)URL segment (e.g., "users")
ResourceKeystringClass nameStable identifier used in contracts and hooks
BackendStorageBackendEfCoreStorage backend type
KeyPropertystring(discovered)Override primary key property discovery ([CrudKey]Id{TypeName}Id, case-insensitive)
MaxPageSizeint200Maximum items per page for list queries
MaxExpandDepthint1Maximum relation expansion depth
DefaultSortstring?nullSort applied to list queries when the client sends none (e.g., "-createdAt,name"); fields must be sortable. The key is always appended as a final tie-breaker for deterministic paging
EnableListbooltrueEnable GET /api/{route}
EnableGetbooltrueEnable GET /api/{route}/{id}
EnableCreatebooltrueEnable POST /api/{route}
EnableUpdatebooltrueEnable PATCH /api/{route}/{id}
EnableDeletebooltrueEnable DELETE /api/{route}/{id}

[CrudKey]

Marks the primary key property.

[CrudKey(ApiName = "id")]
public int Id { get; set; }
PropertyTypeDefaultDescription
ApiNamestringProperty name (camelCase)Override the API-facing key name

Supported key types: int, long, Guid, string.


[CrudField]

Controls field visibility, behavior, and validation.

[CrudField(CrudDto.Read | CrudDto.Create | CrudDto.Update | CrudDto.Filter | CrudDto.Sort,
    ApiName = "email",
    RequiredOnCreate = true,
    Immutable = false,
    Hidden = false,
    Searchable = false,
    DefaultValue = null,
    ComputedExpression = null,
    AllowedValues = null,
    MinLength = null,
    MaxLength = null,
    Min = null,
    Max = null,
    Regex = null)]
public string Email { get; set; } = default!;
PropertyTypeDefaultDescription
dto (positional)CrudDto(required)Flags controlling DTO inclusion and query capabilities
ApiNamestringProperty name (camelCase)Override the API-facing field name
RequiredOnCreateboolfalseField must be present on POST
ImmutableboolfalseField rejected on PATCH (set once on create)
HiddenboolfalseHard-hidden — never exposed in any shape
SearchableboolfalseInclude in full-text search (?q=)
DefaultValueobject?nullDefault value applied when field is omitted on create
ComputedExpressionstring?nullServer-calculated expression (makes field read-only)
AllowedValuesstring?nullPipe-separated allowed values (e.g., "Active|Inactive")
MinLengthint?nullMinimum string length
MaxLengthint?nullMaximum string length
Mindecimal?nullMinimum numeric value
Maxdecimal?nullMaximum numeric value
Regexstring?nullRegular expression pattern

CrudDto Flags

FlagValueEffect
None0Not included in any shape
Read1Included in GET responses
Create2Accepted in POST body
Update4Accepted in PATCH body
Filter8Can be used in filter[field]=value
Sort16Can be used in sort=field

Flags are combinable: CrudDto.Read | CrudDto.Create | CrudDto.Update | CrudDto.Filter | CrudDto.Sort


[CrudRelation]

Configures navigation property behavior for reads and writes.

[CrudRelation(
    Kind = RelationKind.ManyToOne,
    ReadExpandAllowed = true,
    DefaultExpanded = false,
    WriteMode = RelationWriteMode.ById,
    WriteFieldName = "authorId",
    RequiredOnCreate = false,
    ForeignKeyProperty = "AuthorId")]
public User Author { get; set; } = default!;
PropertyTypeDefaultDescription
KindRelationKind(inferred)Cardinality: ManyToOne, OneToMany, ManyToMany, OneToOne
ReadExpandAllowedboolfalseCan use ?expand=relation
DefaultExpandedboolfalseAutomatically expanded without requesting
WriteModeRelationWriteModeNestedDisabledHow writes are performed
WriteFieldNamestring?nullAPI field name for writes (e.g., "authorId")
RequiredOnCreateboolfalseWrite field required on POST
ForeignKeyPropertystring?nullCLR FK property name

RelationWriteMode

ValueDescription
NoneNo write support
ByIdWrite via FK field (e.g., {"authorId": 5})
ByIdListWrite via ID array (e.g., {"tagIds": [1,2,3]})
NestedDisabledNested objects explicitly rejected

[CrudConcurrency]

Marks a row version property for optimistic concurrency.

[CrudConcurrency(RequiredOnUpdate = true)]
public byte[] RowVersion { get; set; } = default!;
PropertyTypeDefaultDescription
ModeConcurrencyModeRowVersionConcurrency mechanism (None, RowVersion, ETag)
RequiredOnUpdatebooltrueWhether If-Match header is required on PATCH/PUT

If the token property has no [CrudField] of its own, it is automatically exposed as a read-only field so clients can obtain the token.


[CrudAuthorize]

Sets authorization policies per operation. Can be applied multiple times. Class-wide policies (no Operation) are applied first, so per-operation policies always win.

[CrudAuthorize("AdminOnly")]
[CrudAuthorize("SuperAdmin", Operation = CrudOperation.Delete)]
public class User { }
PropertyTypeDefaultDescription
policy (positional)string(required)ASP.NET Core authorization policy name
OperationCrudOperation(unset — all operations)Specific operation; HasOperation reports whether it was set

[CrudTenant]

Marks a property as the tenant discriminator for automatic multi-tenancy.

[CrudTenant(ClaimType = "tenant_id", Required = true)]
public string TenantId { get; set; } = default!;
PropertyTypeDefaultDescription
ClaimTypestring"tenant_id"Claim type to extract tenant ID from
RequiredbooltrueReject requests without the tenant claim; if false, operations proceed without tenant filtering

The tenant field is always server-managed: it is forced read-only in the contract and can never be set by clients, regardless of [CrudField] flags.


[CrudHidden]

Completely hides a property from the contract. The field is never exposed in any DTO shape.

[CrudHidden]
public string InternalSecret { get; set; } = default!;

No properties. Apply to any property that should be invisible to the API.


[CrudIgnore]

Excludes a property from contract generation entirely. Use for EF navigation properties or internal properties that should not be processed by DataSurface at all.

[CrudIgnore]
public ICollection<Post> Posts { get; set; } = new List<Post>();

No properties. Differs from [CrudHidden] in that the property is not included in the contract at all, whereas hidden fields are included but marked as hidden.


Default Mapping Rules

ScenarioDefault Behavior
Property without [CrudField]Not exposed via API (safe default)
Navigation without [CrudRelation]Not included in writes, not expanded
Property with [CrudHidden]Hard-hidden from all shapes
Property with [CrudIgnore]Excluded from contract generation