Skip to Content
FeaturesField Types

Field Types

Fields are the building blocks of your content models. When you create a model, you add fields to define what data each record can hold.

Overview

Foir recognizes a fixed set of built-in field types. Each field type has its own editor experience, validation rules, and configuration options. The string you store in a field’s type is the lowercase name of one of these types — anything else is rejected by the platform validator.

All fields support these common options:

OptionDescription
LabelDisplay name shown in the editor
KeyUnique identifier used in APIs (auto-generated from label)
RequiredMust be filled before saving/publishing
Help TextGuidance shown below the field in the editor
Default ValuePre-filled value for new records
QueryableMaterialize the field for fast filtering/sorting (see Queryable fields)
AccessPer-field read/write principal policy (see Field-level access)

The built-in field types

There are fifteen built-in field types:

TypeStoresEditor / widget
textA string (single- or multi-line)Text input (or textarea via config.widget)
numberA numeric valueNumber input
booleanTrue/falseCheckbox
dateA date or date-and-timeDate picker
selectA reference to a record of another modelRecord picker
enumA literal string token from inline optionsEnum select
jsonArbitrary JSONJSON editor
listAn ordered array of itemsArray editor
modelA model key (reference to a model schema)Model picker
referenceA reference to another recordReference picker
fileAn uploaded fileFile picker
imageAn uploaded image with metadataImage picker
videoAn uploaded video with metadataVideo picker
flexibleAn array of structured blocksBlock editor
richtextTranslatable rich-text proseRich-text editor

In addition, any model with inline mode enabled becomes usable as a field type by its own key. See Inline Schemas.

Text Fields

Text

A string field for titles, names, descriptions, and prose. By default it renders as a single-line input. To get a multi-line editor, set config.widget to textarea (see Widgets).

{ "key": "title", "type": "text", "label": "Title", "required": true }

Multi-line:

{ "key": "excerpt", "type": "text", "label": "Excerpt", "config": { "widget": "textarea" } }

Translatable: Yes

Richtext

Translatable rich-text prose — headings, bold/italic, links, lists. Stored as structured JSON. On the public API a richtext field resolves to a RichtextValue object with html, markdown, and json representations, so consumers can render without a specialized renderer.

{ "key": "body", "type": "richtext", "label": "Body" }

richtext is prose only — it has no inline media embeds or structured blocks. For a stack of mixed prose, media, and components, use flexible.

query { page(naturalKey: "home") { body { html markdown } } }

Translatable: Yes

Widgets

Each field type has a default editor widget and, for some types, a small set of allowed alternatives. Override the widget with config.widget. The most common case is rendering a text field as a multi-line textarea:

{ "key": "summary", "type": "text", "config": { "widget": "textarea" } }

text also allows a color widget. Other field types use their default widget.

Structured Content Fields

Flexible

An ordered array of structured blocks — the field type for page bodies, section stacks, and any mixed content layout. Each block is an instance of a block type: either a built-in block primitive or one of your project’s inline-mode models.

{ "key": "body", "type": "flexible", "label": "Body" }

On the public API a flexible field resolves to a list of a Block union. Type-narrow each block in your query:

query { page(naturalKey: "home") { body { __typename ... on RichtextBlock { html markdown } ... on ImageBlock { url alt width height } ... on MediaBlock { url mediaType } ... on ReferenceBlock { modelKey naturalKey record { ... on Author { name avatar { url } } ... on Product { title price } } } ... on Hero { headline ctaUrl } ... on FeatureGrid { features { title description icon { url } } } } } }
function RenderBody({ blocks }: { blocks: BodyBlock[] }) { return ( <> {blocks.map((b) => { switch (b.__typename) { case 'RichtextBlock': return <div key={b._id} dangerouslySetInnerHTML={{ __html: b.html }} />; case 'MediaBlock': return <Media key={b._id} {...b} />; case 'ReferenceBlock':return <Reference key={b._id} record={b.record} />; case 'Hero': return <Hero key={b._id} {...b} />; // ... } })} </> ); }

Built-in block types

These block primitives are always available, no inline-mode model required:

RichtextBlock, ImageBlock, VideoBlock, FileBlock, TextBlock, ReferenceBlock, MediaBlock, FlexibleBlock.

Block allowlist

By default, all inline-mode models in your project (plus the built-in blocks) are available in a flexible field. Restrict to a subset with config.allowedTypes (alias: config.allowedBlockTypes):

{ "key": "body", "type": "flexible", "label": "Body", "config": { "allowedTypes": ["hero", "feature-grid", "testimonial-card"] } }

Translatable: Variants apply at three levels (page / field / block); see Variants.

Media Fields

Image

Upload and manage images with built-in metadata support. Resolves to an ImageValue object with url, width, height, alt, blurhash, dominantColor, mimeType, fileSize, focalPoint, crop, and variants.

{ "key": "featuredImage", "type": "image", "label": "Featured Image" }

Features:

  • Alt text — Text for accessibility
  • Focal point — Set the important area for smart cropping
  • Crops — Per-usage crop geometry
  • Blurhash — Automatically generated placeholder hash
  • Variants — Pre-tokenised size presets (thumbnail, small, medium, large, xlarge)

Translatable: No (alt text is part of the image value)

Video

Upload videos with automatic processing. Resolves to a VideoValue object with url, thumbnailUrl, previewUrl, hlsManifestUrl, width, height, duration, blurhash, dominantColor, mimeType, and fileSize.

{ "key": "heroVideo", "type": "video", "label": "Hero Video" }

Features:

  • Poster image — Thumbnail shown before playback
  • HLS streaming — Automatic manifest generation
  • Duration — Automatically detected on upload

Translatable: No

File

Any file type — PDFs, documents, spreadsheets, downloads. Resolves to a FileValue object with url, filename, mimeType, and size.

{ "key": "attachment", "type": "file", "label": "Attachment" }

Translatable: No

Number, Date, and Boolean Fields

Number

Numeric values for prices, quantities, ratings, and counters.

{ "key": "price", "type": "number", "label": "Price", "required": true }

Translatable: No

Date

Date picker for publish dates, event dates, deadlines, and scheduling. Use the datetime-picker widget for date-and-time.

{ "key": "publishDate", "type": "date", "label": "Publish Date" }

Translatable: No

Boolean

Yes/no toggle for featured flags, visibility settings, and switches.

{ "key": "isFeatured", "type": "boolean", "label": "Featured" }

Translatable: No

Selection Fields

Select (record-backed)

Choose a value from the records of another model. select resolves through the dataloader to the referenced record — it is not an inline list of options. Configure it with optionModelKey (the model whose records are the choices) and, optionally, multiple for multi-select.

{ "key": "language", "type": "select", "label": "Language", "config": { "optionModelKey": "language", "multiple": false } }

Set multiple: true to allow multiple selections (stored as an array).

Translatable: No

Enum (inline options)

Choose from a fixed, inline list of { label, value } options. Unlike select, enum values are literal string tokens stored directly on the record — there is no related model. The storefront receives a typed string-literal union.

{ "key": "category", "type": "enum", "label": "Category", "config": { "options": [ { "label": "News", "value": "news" }, { "label": "Tutorial", "value": "tutorial" }, { "label": "Case Study", "value": "case-study" } ], "multiple": false, "default": "news" } }

Set multiple: true for a multi-select that stores an array of tokens.

Translatable: No

Reference and Model Fields

Reference

Link to another record. Use for authors, related posts, parent pages, and any content relationship.

{ "key": "author", "type": "reference", "label": "Author", "config": { "referenceTypes": ["team-member"] } }

Config options: referenceTypes (array of model keys the field can link to)

Translatable: No

Model

Store a reference to a model schema (the model’s key), rather than a record. Useful for configuration that needs to point at a model type.

{ "key": "targetModel", "type": "model", "label": "Target Model" }

Translatable: No

Structured Data Fields

JSON

Arbitrary JSON data for complex or unstructured values.

{ "key": "metadata", "type": "json", "label": "Metadata" }

Translatable: No

List

Array of items for ordered collections.

{ "key": "bulletPoints", "type": "list", "label": "Bullet Points" }

Translatable: No

Queryable fields

By default, only the always-available direct columns (id, naturalKey, createdAt, etc.) and a small set of indexed fields are fast to filter and sort on. To filter or sort on a declared field, set queryable: true. The platform materializes the field into its index and auto-backfills existing records when you flip the bit.

{ "key": "status", "type": "enum", "label": "Status", "queryable": true, "config": { "options": [ ... ] } }

Flip queryable on text, number, date, and boolean fields that show up in frequent filters or sorts. See Records → Filtering.

Field-level access

Each field can carry an optional access policy with read and write principal lists. Absent, a field is writable by any authenticated principal and readable per the model’s public-field rules. A non-empty write list restricts writes to the named principals; the platform enforces this on every record write path.

{ "key": "internalNotes", "type": "text", "label": "Internal Notes", "access": { "write": ["admin"] } }

Translatable Fields Summary

Fields that support per-locale translations:

Field TypeTranslatable
TextYes
RichtextYes
NumberNo
BooleanNo
DateNo
SelectNo
EnumNo
JSONNo
ListNo
ModelNo
ReferenceNo
FileNo
ImageNo
VideoNo
FlexibleVariant overrides apply (page/field/block)

See Localization for details on how translations work.

In the Admin

Adding fields to a model

  1. Go to Models
  2. Select or create a model
  3. Click Add Field
  4. Choose the field type
  5. Configure options (label, required, help text, widget, type-specific settings)
  6. Save the model

Custom field types

You can create your own composite field types by defining a model with inline mode enabled. For example, create a “Button” model with text, URL, and style fields, then use it as a field type on any model.

See Inline Schemas for details.

Best Practices

  • Mark fields as required only when truly necessary. Optional fields give content teams more flexibility.
  • Use help text to guide editors on format expectations, character limits, or content guidelines.
  • Choose the most specific field type for the data. Use number for prices (not text), date for dates, and boolean for toggles.
  • Use enum for a fixed small set of tokens; use select when the choices are themselves records you manage in another model.
  • Set queryable: true on any field you need to filter or sort by on the API.
  • Enable translations on fields that will need localized content. You can enable this later, but planning ahead saves migration effort.
  • Use references instead of duplicating content. Link to a shared “Author” record rather than entering the author name on every post.
Last updated on