A content type is a named collection of field definitions. Creating one generates a matching set of GraphQL queries and mutations; changing one regenerates the schema automatically.

Defining a type

The apiId must match ^[a-zA-Z][a-zA-Z0-9_]*$ and cannot collide with a reserved name or another type’s generated GraphQL fields.

Field types

TypeStoresNotes
stringshort text
textlong text
richtextlong textyour frontend decides how to render it
intinteger
floatnumber
booleantrue/false
datetimeISO-8601 string
jsonany JSON valuenot filterable or unique
enumone of valuesrequires a values array
mediaURL stringbring your own storage (R2, S3, Cloudinary)
relationentry id(s)requires target; add many: true for a list

Field options

OptionEffect
requiredMust be present on create and at publish time.
uniqueEnforced by a database index — duplicate values are rejected.
indexedAdds an index so filtering on the field stays cheap.
defaultApplied when the field is omitted. Validated against the field type.
Mark any field you filter on frequently as indexed. On Cloudflare D1, an unindexed filter scans every entry of that type, which counts against your daily rows-read budget.

Relations

Relations store the target entry’s id (or an array of ids when many). They resolve to the related entry type in GraphQL, with per-request batching so a list query never triggers N+1 lookups.
Deleting a content type deletes all of its entries and is refused while another type has a relation pointing at it. Remove those relations first.

Validation

On every create and update, the payload is checked against the field definitions: unknown fields are rejected, each value is type-checked, required and enum rules are enforced, unique values are checked against the database, and relation targets must exist.