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
| Type | Description | Example Use |
|---|---|---|
text | Single-line text | Titles, names, slugs |
textarea | Multi-line plain text | Descriptions, excerpts |
content | Unified rich-text + structured-blocks editor | Blog body, long-form pages, marketing content |
number | Numeric values | Prices, quantities, priorities |
boolean | Yes/no toggle | Feature flags, visibility |
date | Date picker (can include time) | Publish dates, event dates |
select | Choose from a list of options (defineSelectField) | Status, category, type |
image | Image upload | Hero images, thumbnails |
video | Video upload (HLS auto-generated on paid plans) | Embedded videos |
file | Generic file upload | PDFs, documents |
link | Internal or external URL | Navigation links |
reference | Reference to another record | Author, related posts |
list | Ordered list of items (itemType controls inner type) | Bullet points, tag arrays |
json | Raw JSON | Untyped 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
},
}| Option | Type | Description |
|---|---|---|
publicApi | boolean | When 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
- Configuration Reference — Full API reference
- Defining Operations — Add operations to process model data
- Defining Hooks — Trigger actions on model lifecycle events
- Field Types — Detailed field type documentation
Last updated on