Server API: Lifecycle
Page summary:
The Server API has 3 lifecycle functions. Use
register()to declare capabilities before the app is fully initialized,bootstrap()to run logic once Strapi is initialized, anddestroy()to clean up resources on shutdown. Each function receives{ strapi }as its argument.
Lifecycle functions control when your plugin's server-side logic runs during the Strapi application startup and shutdown sequence. They are exported from the server entry file alongside routes, controllers, services, and other server blocks.
Before diving deeper into the concepts on this page, please ensure you have:
- created a Strapi plugin,
- read and understood the basics of the Server API
Startup sequence
Understanding when each lifecycle runs helps you put the right code in the right place:
| Phase | What is available in your plugin |
|---|---|
| 2. Register | strapi object is available, but the database is not initialized yet and routing is not initialized yet |
| 4. Bootstrap | Full runtime: database initialized, routes initialized, services and content-types loaded, other plugins available |
| 5. Shutdown | Shutdown is in progress; use this hook to release resources before Strapi finishes stopping |
Each lifecycle function is called once per plugin instance. If a lifecycle is called a second time on the same plugin instance (for example in custom tests), Strapi throws an error. This does not occur under normal operation.
register()
Type: Function
register() runs early in startup, before database initialization and before route initialization.
Use register() to:
- Register the server side of custom fields
- Register database migrations
- Register server middlewares on the Strapi HTTP server (e.g.
strapi.server.use(...)) - Extend another plugin's content-types or interface before bootstrap
- JavaScript
- TypeScript
'use strict';
module.exports = ({ strapi }) => {
// Register a server-level middleware early in startup
strapi.server.use(async (ctx, next) => {
ctx.set('X-Plugin-Version', '1.0.0');
await next();
});
};
import type { Core } from '@strapi/strapi';
export default ({ strapi }: { strapi: Core.Strapi }) => {
// Register a server-level middleware early in startup
strapi.server.use(async (ctx: any, next: () => Promise<void>) => {
ctx.set('X-Plugin-Version', '1.0.0');
await next();
});
};
bootstrap()
Type: Function
bootstrap() runs after module lifecycle registration (plugins/APIs), database initialization, route initialization, and Content API action registration.
Use bootstrap() to:
- Seed the database with initial data
- Register admin RBAC actions using
strapi.service('admin::permission').actionProvider.registerMany(...) - Register cron jobs
- Subscribe to database lifecycle events
- Call services from your plugin or other plugins
- Set up cross-plugin integrations that require other plugins to be registered first
- JavaScript
- TypeScript
'use strict';
module.exports = async ({ strapi }) => {
// Register admin RBAC actions for this plugin
await strapi.service('admin::permission').actionProvider.registerMany([
{
section: 'plugins',
displayName: 'Read',
uid: 'read',
pluginName: 'my-plugin',
},
{
section: 'plugins',
displayName: 'Settings',
uid: 'settings',
pluginName: 'my-plugin',
},
]);
};
import type { Core } from '@strapi/strapi';
export default async ({ strapi }: { strapi: Core.Strapi }) => {
// Register admin RBAC actions for this plugin
await strapi.service('admin::permission').actionProvider.registerMany([
{
section: 'plugins',
displayName: 'Read',
uid: 'read',
pluginName: 'my-plugin',
},
{
section: 'plugins',
displayName: 'Settings',
uid: 'settings',
pluginName: 'my-plugin',
},
]);
};
destroy()
Type: Function
destroy() is called when the Strapi instance is shutting down. It is optional. Only implement it when your plugin holds resources that need explicit cleanup.
Use destroy() to:
- Close external connections (databases, message queues, WebSocket servers)
- Clear intervals or timeouts set in
bootstrap() - Remove event listeners registered during the plugin's lifetime
- JavaScript
- TypeScript
'use strict';
module.exports = ({ strapi }) => {
// Close an external connection opened in bootstrap()
strapi.plugin('my-plugin').service('queue').disconnect();
};
import type { Core } from '@strapi/strapi';
export default ({ strapi }: { strapi: Core.Strapi }) => {
// Close an external connection opened in bootstrap()
strapi.plugin('my-plugin').service('queue').disconnect();
};
Best practices
-
Keep
register()lightweight. It runs before full initialization. -
Use
bootstrap()for database reads/writes. The database is initialized during the bootstrap phase, not during register. Any call tostrapi.documents()or a service that queries the database belongs inbootstrap(). -
Register admin RBAC actions in
bootstrap(). Usestrapi.service('admin::permission').actionProvider.registerMany(...)inbootstrap(). This is when the permission service is available. Content API actions are registered automatically by Strapi during the same phase. -
Always pair resource creation with
destroy(). If your plugin opens a connection, registers a global interval, or attaches a process listener inbootstrap(), implementdestroy()to clean up those resources. This prevents resource leaks during testing and graceful restarts. -
Avoid hard dependencies between plugins in
register(). At registration time, the order in which other plugins have registered is not guaranteed. Cross-plugin calls that rely on another plugin being initialized belong inbootstrap(). -
Prefer services over inline logic. Move non-trivial bootstrap logic into a dedicated service method (e.g.
strapi.plugin('my-plugin').service('setup').initialize()). This keeps lifecycle files readable and the logic testable.