Skip to Content
Config SystemDefining Models

Defining Models

Models define the content types in your project. Each model has a key, a name, and a set of typed fields that describe what data it holds.

Model Interface

interface ApplyConfigModelInput { key: string; // Unique identifier (e.g., 'blog-post') name: string; // Display name (e.g., 'Blog Post') fields?: FieldDefinitionInput[]; // Field definitions config?: Record<string, unknown>; // Additional model configuration }

Basic Example

import { defineConfig, defineModel } from '@eide/foir-cli/configs'; export default defineConfig({ key: 'my-blog', name: 'My Blog', models: [ defineModel({ key: 'blog-post', name: 'Blog Post', fields: [ { key: 'title', type: 'text', label: 'Title', required: true }, { key: 'slug', type: 'text', label: 'Slug', required: true }, { key: 'excerpt', type: 'textarea', label: 'Excerpt' }, { key: 'body', type: 'content', label: 'Body' }, { key: 'publishDate', type: 'date', label: 'Publish Date' }, { key: 'featured', type: 'boolean', label: 'Featured' }, ], }), ], });

Field Definitions

Each field in a model is defined with the FieldDefinitionInput interface:

interface FieldDefinitionInput { key: string; // Unique field key within the model type: string; // Field type (see table below) label?: string; // Display label in the editor required?: boolean; // Whether the field must be filled helpText?: string; // Guidance shown below the field placeholder?: string; // Placeholder text for input fields config?: Record<string, unknown>; // Type-specific configuration itemType?: string; // Item type for list fields storage?: string; // Storage configuration templateZone?: string; // Template zone assignment zoneOrder?: number; // Order within the template zone }

Using defineField

The defineField helper is useful when you want to reuse field definitions across multiple models:

import { defineConfig, defineField } from '@eide/foir-cli/configs'; const seoTitle = defineField({ key: 'seoTitle', type: 'text', label: 'SEO Title', helpText: 'Appears in search engine results. Keep under 60 characters.', placeholder: 'Enter SEO title...', }); const seoDescription = defineField({ key: 'seoDescription', type: 'textarea', label: 'SEO Description', helpText: 'Appears in search engine results. Keep under 160 characters.', }); export default defineConfig({ key: 'my-site', name: 'My Site', models: [ { key: 'page', name: 'Page', fields: [seoTitle, seoDescription] }, { key: 'blog-post', name: 'Blog Post', fields: [seoTitle, seoDescription] }, ], });

Available Field Types

TypeDescriptionExample Use
textSingle-line textTitles, names, slugs
textareaMulti-line plain textDescriptions, excerpts
contentUnified rich-text + structured-blocks editorBlog body, long-form pages, marketing content
numberNumeric valuesPrices, quantities, priorities
booleanYes/no toggleFeature flags, visibility
dateDate picker (can include time)Publish dates, event dates
selectChoose from a list of options (defineSelectField)Status, category, type
imageImage uploadHero images, thumbnails
videoVideo upload (HLS auto-generated on paid plans)Embedded videos
fileGeneric file uploadPDFs, documents
linkInternal or external URLNavigation links
referenceReference to another recordAuthor, related posts
listOrdered list of items (itemType controls inner type)Bullet points, tag arrays
jsonRaw JSONUntyped payloads

The content field replaces the older richtext and flexible types. See Field Types for full details on each type and the content field’s resolved segment shape.

Select Fields

Select fields require an options array in the config property:

{ key: 'status', type: 'select', label: 'Status', required: true, config: { options: [ { value: 'draft', label: 'Draft' }, { value: 'review', label: 'In Review' }, { value: 'published', label: 'Published' }, { value: 'archived', label: 'Archived' }, ], }, }

Model Config Options

The config object on a model supports additional behavior flags:

{ key: 'product', name: 'Product', fields: [...], config: { publicApi: true, // Expose this model via the public API }, }
OptionTypeDescription
publicApibooleanWhen true, records of this model are available through the public content API

Real-World Example

From the Cloudflare KV Redirect extension — a redirect model with text, select, number, and date fields:

{ key: 'redirect', name: 'Redirect', fields: [ { key: 'displayName', type: 'text', label: 'Display Name', required: false }, { key: 'sourcePattern', type: 'text', label: 'Source Pattern', required: true }, { key: 'targetPattern', type: 'text', label: 'Target Pattern', required: true }, { key: 'statusCode', type: 'select', label: 'Status Code', required: true, config: { options: [ { value: '301', label: '301 - Permanent Redirect' }, { value: '302', label: '302 - Temporary Redirect' }, { value: '307', label: '307 - Temporary Redirect (preserve method)' }, { value: '308', label: '308 - Permanent Redirect (preserve method)' }, ], }, }, { key: 'priority', type: 'number', label: 'Priority', required: false }, { key: 'activeFrom', type: 'date', label: 'Active From', required: false }, { key: 'activeTo', type: 'date', label: 'Active Until', required: false }, { key: 'reason', type: 'select', label: 'Reason', required: false, config: { options: [ { value: 'migration', label: 'Site Migration' }, { value: 'rebrand', label: 'Rebrand / Rename' }, { value: 'campaign', label: 'Marketing Campaign' }, { value: 'discontinued', label: 'Discontinued Content' }, { value: 'seo', label: 'SEO Optimization' }, { value: 'other', label: 'Other' }, ], }, }, ], config: { publicApi: true, }, }

Next Steps

Last updated on