Admin Panel API: Hooks
Page summary:
The Hooks API lets plugins create extension points (
createHookinregister) and subscribe to them (registerHookinbootstrap). Hooks run in series, waterfall, or parallel. Strapi includes predefined hooks for the Content Manager's List and Edit views.
The Hooks API allows a plugin to create and register hooks, i.e. places in the application where plugins can add personalized behavior.
Before diving deeper into the concepts on this page, please ensure you have:
- created a Strapi plugin,
- read and understood the basics of the Admin Panel API
Creating hooks
Create hook extension points with createHook() during the register lifecycle. This declares that your plugin provides an extension point that other plugins can subscribe to.
- JavaScript
- TypeScript
export default {
register(app) {
app.createHook('My-PLUGIN/MY_HOOK');
},
};
import type { StrapiApp } from '@strapi/admin/strapi-admin';
export default {
register(app: StrapiApp) {
app.createHook('My-PLUGIN/MY_HOOK');
},
};
For predictable interoperability between plugins, use stable namespaced hook IDs such as my-plugin/my-hook.
Subscribing to hooks
Subscribe to hooks with registerHook() during the bootstrap lifecycle, once all plugins are loaded. The callback receives arguments from the hook caller and should return the (optionally mutated) data.
- JavaScript
- TypeScript
export default {
bootstrap(app) {
app.registerHook('My-PLUGIN/MY_HOOK', (...args) => {
console.log(args);
// important: return the mutated data
return args;
});
},
};
import type { StrapiApp } from '@strapi/admin/strapi-admin';
export default {
bootstrap(app: StrapiApp) {
app.registerHook('My-PLUGIN/MY_HOOK', (...args: unknown[]) => {
console.log(args);
// important: return the mutated data
return args;
});
},
};
Async callbacks are also supported:
- JavaScript
- TypeScript
export default {
bootstrap(app) {
app.registerHook('My-PLUGIN/MY_HOOK', async (data) => {
const enrichedData = await fetchExternalData(data);
// always return data for waterfall hooks
return enrichedData;
});
},
};
import type { StrapiApp } from '@strapi/admin/strapi-admin';
export default {
bootstrap(app: StrapiApp) {
app.registerHook('My-PLUGIN/MY_HOOK', async (data: unknown) => {
const enrichedData = await fetchExternalData(data);
// always return data for waterfall hooks
return enrichedData;
});
},
};
Running hooks
Hooks can be run in 3 modes:
| Mode | Function | Return value |
|---|---|---|
| Series | runHookSeries | Array of results from each function, in order |
| Parallel | runHookParallel | Array of resolved promise results, in order |
| Waterfall | runHookWaterfall | Single value after all transformations applied sequentially |
For runHookWaterfall, each subscriber must return the transformed value so that the next subscriber in the chain receives it. Failing to return a value will break the chain.
Using predefined hooks
Strapi includes predefined hooks for the Content Manager's List and Edit views.
INJECT-COLUMN-IN-TABLE
The Admin/CM/pages/ListView/inject-column-in-table hook can add or mutate columns in the List View of the Content Manager:
runHookWaterfall(INJECT_COLUMN_IN_TABLE, {
displayedHeaders: ListFieldLayout[],
layout: ListFieldLayout,
});
The following example subscribes to this hook to add a custom "External id" column:
- JavaScript
- TypeScript
export default {
bootstrap(app) {
app.registerHook(
'Admin/CM/pages/ListView/inject-column-in-table',
({ displayedHeaders, layout }) => {
return {
displayedHeaders: [
...displayedHeaders,
{
attribute: { type: 'custom' },
label: 'External id',
name: 'externalId',
searchable: false,
sortable: false,
cellFormatter: (document) => document.externalId,
},
],
layout,
};
}
);
},
};
import type { StrapiApp } from '@strapi/admin/strapi-admin';
export default {
bootstrap(app: StrapiApp) {
app.registerHook(
'Admin/CM/pages/ListView/inject-column-in-table',
({ displayedHeaders, layout }) => {
return {
displayedHeaders: [
...displayedHeaders,
{
attribute: { type: 'custom' },
label: 'External id',
name: 'externalId',
searchable: false,
sortable: false,
cellFormatter: (document) => document.externalId,
},
],
layout,
};
}
);
},
};
ListFieldLayout and ListLayout type definitions:
MUTATE-EDIT-VIEW-LAYOUT
The Admin/CM/pages/EditView/mutate-edit-view-layout hook can mutate the Edit View layout of the Content Manager.
The following example subscribes to this hook to force all fields to full width:
- JavaScript
- TypeScript
export default {
bootstrap(app) {
app.registerHook(
'Admin/CM/pages/EditView/mutate-edit-view-layout',
({ layout, ...rest }) => {
// Force all fields to full width in the default edit layout
const updatedLayout = layout.map((rowGroup) =>
rowGroup.map((row) => row.map((field) => ({ ...field, size: 12 })))
);
return {
...rest,
layout: updatedLayout,
};
}
);
},
};
import type { StrapiApp } from '@strapi/admin/strapi-admin';
export default {
bootstrap(app: StrapiApp) {
app.registerHook(
'Admin/CM/pages/EditView/mutate-edit-view-layout',
({ layout, ...rest }) => {
// Force all fields to full width in the default edit layout
const updatedLayout = layout.map((rowGroup) =>
rowGroup.map((row) => row.map((field) => ({ ...field, size: 12 })))
);
return {
...rest,
layout: updatedLayout,
};
}
);
},
};
EditLayout and EditFieldLayout type definitions:
The EditLayout and ListLayout shapes documented here come from the useDocumentLayout hook (see source code). Internal package naming can vary, but plugin authors should rely on the EditLayout and ListLayout shapes exposed in this page.