Skip to Content
Config SystemDefining Hooks

Defining Hooks

Hooks trigger operations automatically when content changes. When a record is created, updated, deleted, or published, hooks fire the operations you configure.

Hook Interface

The typed interface defines the fields that TypeScript validates at compile time:

interface ApplyConfigHookInput { key?: string; // Unique identifier for the hook name?: string; // Display name event: string; // Lifecycle event to listen for operationKey?: string; // Key of the operation to trigger filter?: Record<string, unknown>; // Scope the hook (e.g., { modelKey: 'redirect' }) type?: string; // Hook type url?: string; // Endpoint URL (alternative to operationKey) method?: string; // HTTP method async?: boolean; // Fire-and-forget (true) or wait for result (false) headers?: Record<string, string>; // Custom request headers additionalData?: Record<string, unknown>; // Extra data sent with the payload expression?: Record<string, unknown>; // Conditional expression hooks?: ApplyConfigHookInput[]; // Nested hooks }

Lifecycle Events

Record events:

EventWhen It Fires
RECORD_CREATEDA new record is created
RECORD_UPDATEDAn existing record is updated
RECORD_DELETEDA record is deleted
RECORD_PUBLISHEDA record version is published
RECORD_UNPUBLISHEDA published version is unpublished

Operation events — fired when an operation execution reaches a terminal state:

EventWhen It Fires
OPERATION_COMPLETEDOperation finished successfully (payload includes the typed result)
OPERATION_FAILEDOperation errored (payload includes {code, message, retryable})
OPERATION_CANCELLEDOperation cancelled before completing
OPERATION_TIMED_OUTAsync-callback operation didn’t call back in time

See Lifecycle Hooks for the full event catalog and payload shapes.

Basic Example

Trigger an operation whenever a record is created:

import { defineConfig } from '@eide/foir-cli/configs'; export default defineConfig({ key: 'my-app', name: 'My App', operations: [ { key: 'notify-team', name: 'Notify Team', endpoint: '/notify', }, ], hooks: [ { key: 'notify-on-create', name: 'Notify on new content', event: 'RECORD_CREATED', operationKey: 'notify-team', }, ], });

Filtering by Model

Use the filter field to scope a hook to specific models. Without a filter, the hook fires for all models.

hooks: [ { key: 'sync-on-update', name: 'Sync products on update', event: 'RECORD_UPDATED', operationKey: 'sync-products', filter: { modelKey: 'product' }, }, ]

This hook only fires when a product record is updated. Changes to other models are ignored.

Covering Multiple Events

A common pattern is to create hooks for create, update, and delete events on the same model, all pointing to the same operation:

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' }, }, ]

This ensures the external system stays in sync regardless of how the record changes.

Async vs Sync Hooks

Control whether the platform waits for the operation to complete:

hooks: [ { key: 'validate-before-save', name: 'Validate content', event: 'RECORD_CREATED', operationKey: 'validate-content', async: false, // Platform waits for the result }, { key: 'notify-slack', name: 'Notify Slack', event: 'RECORD_PUBLISHED', operationKey: 'slack-notify', async: true, // Fire and forget }, ]
ModeBehaviorUse Case
async: trueEnqueued to a background job queueNotifications, external syncs
async: falseExecuted inline, result awaitedValidation, critical transformations

Real-World Example

From the Cloudflare KV Redirect extension — three hooks keep edge redirects in sync with content changes:

import { defineConfig } from '@eide/foir-cli/configs'; export default defineConfig({ key: 'cloudflare-kv', name: 'Cloudflare KV Redirector', operations: [ { key: 'redirect-sync', name: 'Sync Redirects to Edge', description: 'Syncs redirect records to Cloudflare KV', endpoint: '/sync/all', }, ], 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' }, }, ], });

Next Steps

Last updated on