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]: unknownindex 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:
| Type | Description |
|---|---|
text | Single-line text |
textarea | Multi-line plain text |
content | Unified rich-text + blocks editor (Lexical-based). Replaces richtext and flexible. |
number | Numeric values |
boolean | Yes/no toggle |
date | Date picker (can include time) |
select | Dropdown selection — see defineSelectField |
image | Image upload with alt text and focal point |
video | Video upload with poster and HLS streaming |
file | Generic file upload |
link | Internal or external link |
reference | Reference to another record |
list | Ordered list of items (itemType controls the inner type) |
json | Raw 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
- Defining Models — Model and field definition details
- Defining Operations — Registering operation endpoints
- Defining Hooks — Triggering operations on lifecycle events
- Editor Placements — Embedding custom UI in the editor
- Defining Apps — Installing apps from manifest URLs
- Push and Remove — Deploying your config