Skip to Content
Config SystemConfiguration Reference

Configuration Reference

Complete reference for the foir.config.ts file format, all available interfaces, and helper functions.

ApplyConfigInput

The top-level config object passed to defineConfig. This is the shape of your entire config file.

interface ApplyConfigInput { key: string; // Unique identifier for this config name: string; // Display name configType?: string; // Config type identifier force?: boolean; // Force reinstall (delete and recreate) /** Base URL prepended to relative operation endpoints at push time. */ operationBaseUrl?: string; models?: ApplyConfigModelInput[]; // Content models operations?: ApplyConfigOperationInput[]; // Operation endpoints segments?: ApplyConfigSegmentInput[]; // Customer segments schedules?: ApplyConfigScheduleInput[]; // Cron schedules hooks?: ApplyConfigHookInput[]; // Lifecycle hooks authProviders?: ApplyConfigAuthProviderInput[]; // Auth providers placements?: ApplyConfigPlacementInput[]; // Editor placements apiKeys?: ApplyConfigApiKeyInput[]; // API keys provisioned at push time /** Per-project app installations, keyed by app name. See /config/apps. */ apps?: Record<string, AppInput>; [key: string]: unknown; // Additional fields accepted at runtime }

All arrays/maps are optional. Include only what your project needs.

Note: The typed interfaces include a [key: string]: unknown index signature, so additional fields beyond the ones listed here are accepted at runtime and passed through to the platform API.

Helper Functions

Every helper function provides TypeScript IntelliSense and compile-time validation. They accept and return the same object, so using them is optional but recommended.

defineConfig

Wraps the top-level config object.

import { defineConfig } from '@eide/foir-cli/configs'; export default defineConfig({ key: 'my-app', name: 'My App', models: [...], operations: [...], });

defineModel

Defines a content model with typed fields.

import { defineModel } from '@eide/foir-cli/configs'; const page = defineModel({ key: 'page', name: 'Page', fields: [ { key: 'title', type: 'text', label: 'Title', required: true }, ], });

defineField

Defines a single field definition. Useful when building fields programmatically or reusing field definitions across models.

import { defineField } from '@eide/foir-cli/configs'; const titleField = defineField({ key: 'title', type: 'text', label: 'Title', required: true, placeholder: 'Enter a title...', });

defineSelectField

Defines a select field. Required for select fields because they need a typed optionModelKey pointing at a model whose records become the options. Plain defineField({ type: 'select' }) is rejected.

import { defineSelectField } from '@eide/foir-cli/configs'; const status = defineSelectField({ key: 'status', type: 'select', label: 'Status', required: true, config: { optionModelKey: 'status-option', // a model with records like 'draft', 'published' multiple: false, }, });

defineOperation

Defines an HTTP endpoint for custom logic.

import { defineOperation } from '@eide/foir-cli/configs'; const syncOp = defineOperation({ key: 'sync-data', name: 'Sync Data', description: 'Syncs records to external system', endpoint: '/sync/all', });

defineHook

Defines a lifecycle hook that triggers an operation on content events.

import { defineHook } from '@eide/foir-cli/configs'; const hook = defineHook({ event: 'RECORD_CREATED', type: 'operation', url: '/sync/all', });

definePlacement

Defines a custom editor panel.

import { definePlacement } from '@eide/foir-cli/configs'; const editorTab = definePlacement({ type: 'main-editor', url: '/', modelKeys: ['page'], tabName: 'Custom Editor', });

defineSchedule

Defines a cron schedule for an operation.

import { defineSchedule } from '@eide/foir-cli/configs'; const nightly = defineSchedule({ operationKey: 'sync-data', cron: '0 2 * * *', timezone: 'America/New_York', enabled: true, });

defineSegment

Defines a customer segment with rules.

import { defineSegment } from '@eide/foir-cli/configs'; const vips = defineSegment({ key: 'vip-customers', name: 'VIP Customers', description: 'High-value customers', rules: { type: 'condition', left: { type: 'field', path: 'totalSpend' }, operator: 'greaterThan', right: { type: 'literal', value: 500 }, }, isActive: true, });

defineAuthProvider

Defines an authentication provider.

import { defineAuthProvider } from '@eide/foir-cli/configs'; const auth = defineAuthProvider({ key: 'google-oauth', name: 'Google OAuth', type: 'oauth2', config: { clientId: process.env.GOOGLE_CLIENT_ID, allowedDomains: ['example.com'], }, enabled: true, isDefault: false, priority: 10, });

Field Types

The following field types are available for model field definitions:

TypeDescription
textSingle-line text
textareaMulti-line plain text
contentUnified rich-text + blocks editor (Lexical-based). Replaces richtext and flexible.
numberNumeric values
booleanYes/no toggle
dateDate picker (can include time)
selectDropdown selection — see defineSelectField
imageImage upload with alt text and focal point
videoVideo upload with poster and HLS streaming
fileGeneric file upload
linkInternal or external link
referenceReference to another record
listOrdered list of items (itemType controls the inner type)
jsonRaw JSON

See Field Types for detailed descriptions of each type.

Complete Example

This is the full config for the Cloudflare KV Redirect extension, showing models, operations, hooks, and placements working together:

import { defineConfig } from '@eide/foir-cli/configs'; export default defineConfig({ key: 'cloudflare-kv', name: 'Cloudflare KV Redirector', configType: 'cloudflare-kv', models: [ { 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, }, }, ], hooks: [ { key: 'sync-on-create', name: 'Sync on redirect create', event: 'RECORD_CREATED', operationKey: 'redirect-sync', filter: { modelKey: 'redirect' }, }, { key: 'sync-on-update', name: 'Sync on redirect update', event: 'RECORD_UPDATED', operationKey: 'redirect-sync', filter: { modelKey: 'redirect' }, }, { key: 'sync-on-delete', name: 'Sync on redirect delete', event: 'RECORD_DELETED', operationKey: 'redirect-sync', filter: { modelKey: 'redirect' }, }, ], operations: [ { key: 'redirect-sync', name: 'Sync Redirects to Edge', description: 'Syncs redirect records to Cloudflare KV', endpoint: '/sync/all', }, { key: 'redirect-sync-status', name: 'Get Sync Status', description: 'Returns current sync metadata for display in the editor', endpoint: '/sync/status', }, { key: 'redirect-undeploy', name: 'Undeploy Edge Redirects', description: 'Tear down Cloudflare Worker and KV namespace', endpoint: '/deploy/destroy', }, ], placements: [ { type: 'main-editor', url: '/', modelKeys: ['redirect'], hideContentTab: true, tabName: 'Redirect Editor', }, ], });

ApiKey Provisioning

apiKeys declares API keys the CLI provisions during foir push. The created key value is written into your project’s .env file under the named environment variable so your application can pick it up immediately.

interface ApplyConfigApiKeyInput { /** Display name (e.g. "Tilly iOS"). */ name: string; /** 'public' for client apps, 'secret' for server/BFF use. */ keyType: 'public' | 'secret'; /** Env var the CLI writes the key value to (e.g. "FOIR_PUBLIC_KEY"). */ envVar: string; /** Scopes — use ["*"] for full access. */ scopes?: string[]; /** Restrict to specific model keys. */ allowedModels?: string[]; /** Restrict file uploads to specific MIME types. */ allowedFileTypes?: string[]; }
export default defineConfig({ key: 'tilly', name: 'Tilly', apiKeys: [ { name: 'Tilly iOS', keyType: 'public', envVar: 'TILLY_PUBLIC_KEY', scopes: ['records:read', 'records:write:tilly_note'], allowedModels: ['tilly_note', 'tilly_block'], }, { name: 'Tilly BFF', keyType: 'secret', envVar: 'TILLY_SECRET_KEY', scopes: ['*'], }, ], });

Apps

Per-project app installations live under the top-level apps key. Each entry maps a manifest URL to the project’s models and any per-project settings. See Defining Apps for the full reference.

interface AppInput { source: string; // https URL to the manifest JSON settings?: Record<string, unknown>; mappings?: { sources?: Record<string, AppSourceMappingInput>; sinks?: Record<string, AppSinkMappingInput>; placementFields?: Record<string, AppPlacementFieldChoiceInput>; }; } interface AppSourceMappingInput { toModel: string; naturalKey: string; fields: Record<string, string>; // app-field-name → model-field-key } interface AppSinkMappingInput { toModel: string; naturalKey: string; fields: Record<string, string>; } interface AppPlacementFieldChoiceInput { model: string; field: string; }

Next Steps

Last updated on