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 provides a set of built-in field types that cover the most common content needs. Each field type has its own editor experience, validation rules, and configuration options.
All fields support these common options:
| Option | Description |
|---|---|
| Label | Display name shown in the editor |
| Key | Unique identifier used in APIs (auto-generated from label) |
| Required | Must be filled before saving/publishing |
| Help Text | Guidance shown below the field in the editor |
| Default Value | Pre-filled value for new records |
Text Fields
Text
Single-line text for titles, names, and short content.
{
"key": "title",
"type": "text",
"label": "Title",
"required": true
}Config options: minLength, maxLength, pattern (regex validation)
Translatable: Yes
Textarea
Multi-line plain text — descriptions, excerpts, anything that doesn’t need formatting.
{
"key": "excerpt",
"type": "textarea",
"label": "Excerpt"
}Translatable: Yes
Content
The unified rich-text + structured-blocks editor. Replaces both richtext and flexible as separate field types. One field, one editing surface, mixes prose and inline blocks.
{
"key": "body",
"type": "content",
"label": "Body"
}What “content” is
Stored as a Lexical EditorState (a JSON tree). At read time the platform resolves it into an array of typed segments your client can render directly. Each segment is one of:
_type | Shape | What it is |
|---|---|---|
richtext | { _type, _id, html, markdown, json } | A run of prose grouped into one segment. The platform pre-renders both HTML and Markdown so consumers don’t need a Lexical renderer. |
<inlineModelKey> | { _type, _id, ...resolvedFields } | An inline block — a record of an inline-mode model embedded inline in the content. Fields are resolved through the normal field pipeline (so an image field on the block returns a full ImageValue, etc.). |
media | { _type, _id, mediaType, url, ..., alt? } | An embedded image, video, or file. mediaType is normalised from the file’s MIME, so legacy records with miscategorised media self-heal. Per-usage alt text is preserved. |
reference | { _type, _id, _schema, modelKey, naturalKey, title?, record? } | A reference to another record. record is a Record union resolving to the concrete user model type, so consumers can select typed fields inline. |
Rendering on the client
query {
page(naturalKey: "home") {
body {
__typename
... on RichTextBlock { html markdown }
... on MediaBlock { url alt 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} />;
// ...
}
})}
</>
);
}Block allowlist
By default, all inline-mode models in your project are available as blocks. Restrict to a subset with config.blocks:
{
"key": "body",
"type": "content",
"label": "Body",
"config": {
"blocks": ["hero", "feature-grid", "testimonial-card"]
}
}Translatable
Yes — content fields support per-locale translations like richtext did. Variants apply at three levels (page / content-field / block); see Variants for the model.
Email address field with built-in validation.
{
"key": "contactEmail",
"type": "email",
"label": "Contact Email"
}Translatable: No
Phone
Phone number field.
{
"key": "phone",
"type": "phone",
"label": "Phone Number"
}Translatable: No
URL
URL field with built-in validation.
{
"key": "website",
"type": "url",
"label": "Website"
}Translatable: No
Media Fields
Image
Upload and manage images with built-in metadata support. Returns an ImageValue object with url, width, height, alt, blurhash, dominantColor, mimeType, fileSize, focalPoint, and crops.
{
"key": "featuredImage",
"type": "image",
"label": "Featured Image"
}Features:
- Alt text — Text for accessibility
- Focal point — Set the important area for smart cropping
- Crops — Pre-defined crop variants (square, landscape, etc.)
- Blurhash — Automatically generated placeholder hash
Translatable: No (alt text is part of the image value)
Video
Upload videos with automatic processing. Returns a VideoValue object with url, thumbnailUrl, previewUrl, hlsManifestUrl, width, height, duration, 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. Returns a FileValue object with url, filename, mimeType, and size.
{
"key": "attachment",
"type": "file",
"label": "Attachment"
}Translatable: No
Number and Date Fields
Number
Numeric values for prices, quantities, ratings, and counters.
{
"key": "price",
"type": "number",
"label": "Price",
"required": true
}Config options: min, max, step, decimals
Translatable: No
Currency
Monetary values with currency formatting.
{
"key": "totalPrice",
"type": "currency",
"label": "Total Price"
}Returns a CurrencyValue object.
Translatable: No
Date
Date picker for publish dates, event dates, deadlines, and scheduling.
{
"key": "publishDate",
"type": "date",
"label": "Publish Date"
}Config options: includeTime (enables time selection)
Translatable: No
Boolean
Yes/no toggle for featured flags, visibility settings, and switches.
{
"key": "isFeatured",
"type": "boolean",
"label": "Featured"
}Translatable: No
Selection Fields
Select
Choose a single option from a predefined list.
{
"key": "category",
"type": "select",
"label": "Category",
"config": {
"options": [
{ "label": "News", "value": "news" },
{ "label": "Tutorial", "value": "tutorial" },
{ "label": "Case Study", "value": "case-study" }
]
}
}Translatable: No
Multiselect
Choose multiple options from a predefined list.
{
"key": "tags",
"type": "multiselect",
"label": "Tags",
"config": {
"options": [
{ "label": "Featured", "value": "featured" },
{ "label": "Popular", "value": "popular" },
{ "label": "New", "value": "new" }
]
}
}Returns an array of selected values.
Translatable: No
Reference 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 to link to)
Translatable: No
Link Fields
Link
Internal or external links with structured data.
{
"key": "cta",
"type": "link",
"label": "Call to Action"
}Features:
- Link text — The visible label
- URL or page reference — External URL or internal record reference
- Open in new tab — Target behavior
Supports internal pages, external URLs, email (mailto:), and phone (tel:) links.
Translatable: Yes
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
Translatable Fields Summary
Fields that support per-locale translations:
| Field Type | Translatable |
|---|---|
| Text | Yes |
| Textarea | Yes |
| Content | Yes |
| Link | Yes |
| No | |
| Phone | No |
| URL | No |
| Image | No |
| Video | No |
| File | No |
| Number | No |
| Currency | No |
| Date | No |
| Boolean | No |
| Select | No |
| Multiselect | No |
| Reference | No |
| JSON | No |
| List | No |
See Localization for details on how translations work.
Migrating from richtext / flexible
The unified content field replaced two earlier separate types:
richtext— prose only, no inline blocks. Equivalent to acontentfield whose document happens to contain only prose nodes; the API still returns a singleRichTextBlocksegment.flexible— array of blocks, no prose. Equivalent to acontentfield whose document contains only block / media / reference nodes interspersed with empty prose runs.
Existing records on the old types continue to resolve correctly while your data is migrated. New models should declare type: 'content'.
In the Admin
Adding fields to a model
- Go to Models
- Select or create a model
- Click Add Field
- Choose the field type
- Configure options (label, required, help text, type-specific settings)
- 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
numberfor prices (nottext),datefor dates, andbooleanfor toggles. - 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.