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:
| Event | When It Fires |
|---|---|
RECORD_CREATED | A new record is created |
RECORD_UPDATED | An existing record is updated |
RECORD_DELETED | A record is deleted |
RECORD_PUBLISHED | A record version is published |
RECORD_UNPUBLISHED | A published version is unpublished |
Operation events — fired when an operation execution reaches a terminal state:
| Event | When It Fires |
|---|---|
OPERATION_COMPLETED | Operation finished successfully (payload includes the typed result) |
OPERATION_FAILED | Operation errored (payload includes {code, message, retryable}) |
OPERATION_CANCELLED | Operation cancelled before completing |
OPERATION_TIMED_OUT | Async-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
},
]| Mode | Behavior | Use Case |
|---|---|---|
async: true | Enqueued to a background job queue | Notifications, external syncs |
async: false | Executed inline, result awaited | Validation, 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
- Lifecycle Hooks Concepts — How hooks work under the hood
- Defining Operations — Create the operations that hooks trigger
- Defining Schedules — Trigger operations on a cron schedule instead of events