How to Configure hooks.json for Managing Plugin Lifecycle Events
The hooks.json manifest lets OpenAI plugins declare lifecycle hooks—JavaScript modules that run automatically during load, enable, disable, update, and unload events—allowing deterministic initialization and cleanup without user intervention.
This guide explains how to configure hooks.json to manage plugin lifecycle events in the openai/plugins repository. By defining this optional manifest, you can automate setup routines, resource management, and state transitions across the five key lifecycle stages.
Available Plugin Lifecycle Hooks
The platform recognizes five standard hooks that trigger at specific moments. Configuring these in hooks.json ensures your plugin responds predictably to state changes.
onLoad: Executes immediately when the plugin is discovered, before any user interaction. Use this to validate environment variables, parse configuration, or initialize shared API clients.onEnable: Fires when a user explicitly enables the plugin via the UI or CLI. Ideal for starting background workers, opening WebSocket connections, or scheduling recurring tasks.onDisable: Runs when the plugin is disabled. Implement graceful shutdown logic here—cancel timers, close sockets, and persist ephemeral state.onUnload: Triggered when the plugin is removed from memory (e.g., during hot-reload). Use this to release native resources, delete temporary files, or revoke security tokens.onUpdate: Invoked after the plugin code or assets are updated. Perfect for data migration scripts, cache rehydration, or schema upgrades.
Attempting to declare unsupported hook names will not crash the plugin; the platform ignores unknown entries and logs a warning.
Structure of the hooks.json Manifest
The hooks.json file is a plain JSON object where keys are hook names and values are relative file paths to JavaScript or TypeScript modules. Each module must export a single async function named handler.
{
"onLoad": "./hooks/onLoad.js",
"onEnable": "./hooks/onEnable.js",
"onDisable": "./hooks/onDisable.js"
}
Every hook handler follows this signature:
/** @type {(context: HookContext) => Promise<void>} */
export async function handler(context) {
// Lifecycle logic here
}
The context object provides:
pluginId: The unique identifier for this plugin instance.config: The parsed contents ofplugin.json.logger: A scoped logger that writes to the platform's diagnostic console.state: A shared mutable object for storing data across hooks (e.g., HTTP clients, timers).
If a hook throws an error, the platform logs the failure but continues loading the plugin, preventing a single failed initialization from breaking the entire system.
Linking hooks.json to plugin.json
To activate the lifecycle system, reference hooks.json from your main plugin.json manifest using the "hooks" field. This indirection keeps the hook registry separate from the primary configuration.
{
"name": "My Awesome Plugin",
"version": "0.1.0",
"description": "Demonstrates lifecycle hooks",
"hooks": "./hooks.json"
}
As seen in the repository structure for the Replay.io and Figma plugins (see plugins/replayio/.codex-plugin/plugin.json and plugins/figma/.codex-plugin/plugin.json), this separation simplifies version control and allows automated tools to generate hook manifests independently of the main spec.
File Organization and Repository Examples
Hook modules must reside inside the plugin directory. While conventional projects use a hooks/ subdirectory, any relative path works.
Standard layout:
my-plugin/
├── .codex-plugin/
│ └── plugin.json
├── hooks/
│ ├── onLoad.js
│ ├── onEnable.js
│ └── onDisable.js
└── hooks.json
Real-world implementations in the openai/plugins repository demonstrate this pattern:
plugins/replayio/hooks.json: Defines hooks for the Replay.io integration.plugins/figma/hooks.json: Declares lifecycle handlers for the Figma plugin.
These files illustrate how production plugins centralize lifecycle logic outside their core skill code.
Implementing Hook Handlers
Below are practical implementations for common lifecycle tasks.
Initializing Shared Resources (onLoad)
Use onLoad to create reusable clients and store them on context.state for access in later hooks.
export async function handler({ config, logger, state }) {
logger.info('onLoad: initializing HTTP client');
const baseUrl = config.apiBaseUrl;
const apiKey = config.secrets.API_KEY;
state.http = new HttpClient({
baseUrl,
headers: { Authorization: `Bearer ${apiKey}` }
});
}
Starting Background Jobs (onEnable)
Start polling or scheduled tasks when the plugin becomes active.
export async function handler({ state, logger }) {
logger.info('onEnable: starting background poller');
const poll = async () => {
try {
const resp = await state.http.get('/events');
// Process events...
} catch (e) {
logger.error('poll error', e);
}
};
state.poller = setInterval(poll, 10000);
}
Graceful Shutdown (onDisable)
Clean up resources to prevent memory leaks and orphaned connections.
export async function handler({ state, logger }) {
logger.info('onDisable: stopping background poller');
clearInterval(state.poller);
state.poller = null;
}
Handling Updates (onUpdate)
Migrate data when the plugin version changes.
export async function handler({ config, logger }) {
const storedVersion = await readVersionFromDisk();
if (storedVersion !== config.version) {
logger.info(`onUpdate: migrating from ${storedVersion} → ${config.version}`);
await runMigration(storedVersion, config.version);
await writeVersionToDisk(config.version);
}
}
Testing and Debugging Hooks
Verify hook behavior through unit and integration tests.
Unit Testing: Import the handler directly and invoke it with a mock HookContext object containing spies for logger and state.
Integration Testing: Use the plugin-creator skill located at .agents/skills/plugin-creator/ to scaffold a test harness. Enable and disable the plugin programmatically, asserting that side effects (file writes, network calls) occur as expected.
Debugging Tips:
- Write to
context.logger.info()orcontext.logger.error()—these entries appear in the platform's debug console. - If hooks never execute, verify that
plugin.jsoncorrectly points tohooks.jsonand that all file paths inhooks.jsonresolve relative to the plugin root. - Remember that hooks run once per plugin instance; hot-reloading triggers
onUpdatebut notonLoad.
Summary
hooks.jsonis an optional manifest that maps lifecycle events to JavaScript handler modules.- The platform supports five hooks:
onLoad,onEnable,onDisable,onUnload, andonUpdate. - Each handler receives a
HookContextcontainingpluginId,config,logger, andstate. - Reference
hooks.jsonfromplugin.jsonusing the"hooks"field to activate the system. - Examples in
plugins/replayio/andplugins/figma/demonstrate production-ready configurations.
Frequently Asked Questions
What happens if a hook throws an error?
The platform catches the exception, logs it to the diagnostic console, and continues execution. A failing hook does not prevent the plugin from loading or other hooks from running, making them safe for non-critical operations.
Can I use TypeScript for hook handlers?
Yes. The platform resolves the relative paths in hooks.json regardless of file extension. Ensure your TypeScript files compile to JavaScript before the plugin loads, or use a runtime transpiler if the environment supports it.
How do I share data between different hooks?
Store values on the context.state object provided to every handler. This object persists across the lifecycle of the plugin instance, allowing you to initialize an HTTP client in onLoad and reference it in onEnable or onDisable.
Where is the formal specification for the hooks field?
The complete JSON schema is documented in .agents/skills/plugin-creator/references/plugin-json-spec.md within the repository. This file defines the structure of plugin.json, including the "hooks" field that links to your hooks.json manifest.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →