Beyond the defaults
Plugins
Extend the Routecraft runtime with cross-cutting behaviour.
What is a plugin?
A plugin is code that runs once when the context starts, before any capabilities are registered. It has access to the full CraftContext and can:
- Subscribe to lifecycle events (capability started, error occurred, context stopped)
- Write shared state to the context store for adapters to read
- Register additional capabilities dynamically
Plugins vs capabilities: a capability defines what your system does. A plugin extends how the runtime behaves. Logging, metrics, tracing, auth headers, and connection pooling are all plugin concerns, not capability concerns.
Writing a plugin
A plugin is an object with an apply(context) method. An optional name identifies it in logs and plugin:* events; without one, the runtime derives an id:
// plugins/logger.ts
import { type CraftContext, type CraftPlugin } from '@routecraft/routecraft'
export default {
name: 'logger',
apply(context: CraftContext) {
context.on('route:started', ({ details: { route } }) => {
context.logger.info(`Started: ${route.definition.id}`)
})
context.on('context:error', ({ details: { error } }) => {
context.logger.error(error, 'Context error')
})
},
} satisfies CraftPlugin
Two more phases are optional: start runs once every route has started, and teardown releases what the plugin acquired when the context stops. A plugin with a lifetime, such as a timer, uses them. This one only needs apply, and keeps its state in the context store:
// plugins/metrics.ts
import { type CraftContext, type CraftPlugin } from '@routecraft/routecraft'
// Declaring the key types for what goes in and what comes back out, so
// readers do not have to cast.
declare module '@routecraft/routecraft' {
interface StoreRegistry {
'metrics.counters': { started: number; errors: number }
}
}
export default {
name: 'metrics',
async apply(context: CraftContext) {
context.setStore('metrics.counters', { started: 0, errors: 0 })
context.on('route:started', () => {
const counters = context.getStore('metrics.counters')
if (counters) counters.started += 1
})
},
} satisfies CraftPlugin
Registering a plugin
Pass plugins in craft.config.ts:
// craft.config.ts
import type { CraftConfig } from '@routecraft/routecraft'
import logger from './plugins/logger'
import metrics from './plugins/metrics'
export const craftConfig: CraftConfig = {
plugins: [logger, metrics],
}
Setting global adapter defaults
The most common use of plugins and context configuration is setting default options for adapters so you do not repeat them in every capability.
Core adapters have dedicated fields on CraftConfig:
// craft.config.ts
export const craftConfig: CraftConfig = {
cron: { timezone: 'UTC', maxJitter: 2000 },
direct: { channelType: KafkaChannel },
}
External adapters (from @routecraft/ai, etc.) use companion plugins:
import { llmPlugin } from '@routecraft/ai'
export const craftConfig: CraftConfig = {
cron: { timezone: 'UTC' },
plugins: [
llmPlugin({
providers: { anthropic: { apiKey: process.env.ANTHROPIC_API_KEY! } },
defaultOptions: { temperature: 0.7 },
}),
],
}
Every cron() source and llm() destination in the context inherits those defaults unless overridden per-adapter. This keeps shared configuration out of every capability file.
For the full pattern (how merged options work, which adapters support them, and how to add support to a custom adapter), see Merged options.
Managing external services
Plugins can manage long-lived external processes. The built-in mcpPlugin demonstrates this pattern: it spawns stdio MCP server subprocesses, monitors their health, and restarts them with exponential backoff when they crash.
import { mcpPlugin } from '@routecraft/ai'
export const craftConfig: CraftConfig = {
plugins: [
mcpPlugin({
clients: {
filesystem: {
transport: 'stdio',
command: 'npx',
args: ['-y', '@modelcontextprotocol/server-filesystem', '/tmp'],
},
},
maxRestarts: 5,
}),
],
}
The plugin starts each subprocess when the context starts and tears them down when it stops. Tools from all sources (local routes, stdio clients, HTTP clients) are collected into a unified registry accessible from the context store.
Lifecycle events
Plugins subscribe to events using context.on(eventName, handler). Common events include route:started, route:stopped, context:started, context:stopped, and context:error. See the Events reference for the full list.
Dynamically registering capabilities
Because plugins run before capabilities are registered, they can add capabilities to the context at startup:
// plugins/admin.ts
import {
craft,
log,
simple,
type CraftContext,
type CraftPlugin,
} from '@routecraft/routecraft'
export default {
name: 'admin',
apply(context: CraftContext) {
if (process.env.ENABLE_ADMIN === 'true') {
context.registerRoutes(
craft()
.id('admin-health')
.from(simple({ ok: true }))
.to(log())
.build()[0],
)
}
},
} satisfies CraftPlugin
Related
Monitoring
Logging, telemetry, and writing a custom monitoring plugin.
Merged options
Set adapter defaults once and share them across the context.
Plugins
The reference catalogue: every built-in plugin and its options.