Functions
Page summary:
src/indexhosts global register, bootstrap, and destroy functions to run logic during application lifecycle.
Available modes
Lifecycle functions support 3 execution patterns/modes so you can align them with the dependencies they manage. Strapi waits for each function to finish, whether it returns normally, resolves an async function, or resolves a promise, before moving on with startup or shutdown.
Return values aren't used by Strapi, so the functions should resolve (or return) only when their setup or cleanup is complete and throw or reject to signal a failure.
Synchronous
Synchronous functions run logic that completes immediately without awaiting other asynchronous tasks.
- JavaScript
- TypeScript
module.exports = {
register({ strapi }) {
strapi.log.info('Registering static configuration');
},
bootstrap({ strapi }) {
strapi.log.info('Bootstrap finished without awaiting tasks');
},
destroy({ strapi }) {
strapi.log.info('Server shutdown started');
}
};
export default {
register({ strapi }) {
strapi.log.info('Registering static configuration');
},
bootstrap({ strapi }) {
strapi.log.info('Bootstrap finished without awaiting tasks');
},
destroy({ strapi }) {
strapi.log.info('Server shutdown started');
}
};
Asynchronous
Asynchronous functions use the async keyword to await tasks such as API calls or database queries before Strapi continues.
- JavaScript
- TypeScript
module.exports = {
async register({ strapi }) {
await new Promise((resolve) => setTimeout(resolve, 200));
strapi.log.info('Async register finished after a short delay');
},
async bootstrap({ strapi }) {
const { results: articles } = await strapi
.documents('api::article.article')
.findMany({
filters: { publishedAt: { $notNull: true } },
fields: ['id'],
});
strapi.log.info(`Indexed ${articles.length} published articles`);
},
async destroy({ strapi }) {
await strapi.documents('api::temporary-cache.temporary-cache').deleteMany({
filters: {},
});
}
};
export default {
async register({ strapi }) {
await new Promise((resolve) => setTimeout(resolve, 200));
strapi.log.info('Async register finished after a short delay');
},
async bootstrap({ strapi }) {
const { results: articles } = await strapi
.documents('api::article.article')
.findMany({
filters: { publishedAt: { $notNull: true } },
fields: ['id'],
});
strapi.log.info(`Indexed ${articles.length} published articles`);
},
async destroy({ strapi }) {
await strapi.documents('api::temporary-cache.temporary-cache').deleteMany({
filters: {},
});
}
};
Returning a promise
Promise-returning functions hand back a promise so Strapi can wait for its resolution before continuing.
- JavaScript
- TypeScript
module.exports = {
register({ strapi }) {
return new Promise((resolve) => {
strapi.log.info('Registering with a delayed startup task');
setTimeout(resolve, 200);
});
},
bootstrap({ strapi }) {
return new Promise((resolve, reject) => {
strapi
.documents('api::category.category')
.findMany({ filters: { slug: 'general' }, pageSize: 1 })
.then(({ results }) => {
if (results.length === 0) {
return strapi.documents('api::category.category').create({
data: { name: 'General', slug: 'general' },
});
}
return results[0];
})
.then(() => {
strapi.log.info('Ensured default category exists');
resolve();
})
.catch(reject);
});
},
destroy({ strapi }) {
return new Promise((resolve, reject) => {
strapi
.documents('api::temporary-cache.temporary-cache')
.deleteMany({ filters: {} })
.then(() => {
strapi.log.info('Cleared temporary cache before shutdown');
resolve();
})
.catch(reject);
});
}
};
export default {
register({ strapi }) {
return new Promise((resolve) => {
strapi.log.info('Registering with a delayed startup task');
setTimeout(resolve, 200);
});
},
bootstrap({ strapi }) {
return new Promise((resolve, reject) => {
strapi
.documents('api::category.category')
.findMany({ filters: { slug: 'general' }, pageSize: 1 })
.then(({ results }) => {
if (results.length === 0) {
return strapi.documents('api::category.category').create({
data: { name: 'General', slug: 'general' },
});
}
return results[0];
})
.then(() => {
strapi.log.info('Ensured default category exists');
resolve();
})
.catch(reject);
});
},
destroy({ strapi }) {
return new Promise((resolve, reject) => {
strapi
.documents('api::temporary-cache.temporary-cache')
.deleteMany({ filters: {} })
.then(() => {
strapi.log.info('Cleared temporary cache before shutdown');
resolve();
})
.catch(reject);
});
}
};
Lifecycle functions
Lifecycle functions let you place code at specific phases of Strapi's startup and shutdown.
- The
register()function is for configuration-time setup before services start. - The
bootstrap()function is for initialization that needs Strapi's APIs. - The
destroy()function is for teardown when the application stops.
Register
The register lifecycle function, found in ./src/index.js (or in ./src/index.ts), is an asynchronous function that runs before the application is initialized.
register() is the very first thing that happens when a Strapi application is starting. This happens before any setup process and you don't have any access to database, routes, policies, or any other backend server elements within the register() function.
The register() function can be used to:
- extend plugins
- extend content-types programmatically
- load some environment variables
- register a custom field that would be used only by the current Strapi application,
- register a custom provider for the Users & Permissions plugin.
More specifically, typical use-cases for register() include front-load security tasks such as loading secrets, rotating API keys, or registering authentication providers before the app finishes initializing.
- JavaScript
- TypeScript
module.exports = {
register({ strapi }) {
strapi.customFields.register({
name: 'color',
plugin: 'my-color-picker',
type: 'string',
});
},
};
export default {
register({ strapi }) {
strapi.customFields.register({
name: 'color',
plugin: 'my-color-picker',
type: 'string',
});
},
};
Bootstrap
The bootstrap lifecycle function, found in ./src/index.js (or in ./src/index.ts), is called at every server start.
bootstrap() is run before the back-end server starts but after the Strapi application has setup, so you have access to anything from the strapi object.
The bootstrap function can be used to:
- create an admin user if there isn't one
- fill the database with some necessary data
- declare custom conditions for the Role-Based Access Control (RBAC) feature
More specifically, a typical use-case for bootstrap() is supporting editorial workflows. For example by seeding starter content, attaching webhooks, or scheduling cron jobs at startup.
You can run yarn strapi console (or npm run strapi console) in the terminal and interact with the strapi object.
- JavaScript
- TypeScript
module.exports = {
async bootstrap({ strapi }) {
const { results } = await strapi
.documents('api::category.category')
.findMany({ filters: { slug: 'general' }, pageSize: 1 });
if (results.length === 0) {
await strapi.documents('api::category.category').create({
data: { name: 'General', slug: 'general' },
});
strapi.log.info('Created default category');
}
},
};
export default {
async bootstrap({ strapi }) {
const { results } = await strapi
.documents('api::category.category')
.findMany({ filters: { slug: 'general' }, pageSize: 1 });
if (results.length === 0) {
await strapi.documents('api::category.category').create({
data: { name: 'General', slug: 'general' },
});
strapi.log.info('Created default category');
}
},
};
Destroy
The destroy function, found in ./src/index.js (or in ./src/index.ts), is an asynchronous function that runs before the application gets shut down.
The destroy function can be used to gracefully:
- stop services that are running
- clean up plugin actions (e.g. close connections, remove listeners, etc.)
More specifically, a typical use-case for destroy() is to handle operational clean-up, such as closing database or queue connections and removing listeners so the application can shut down cleanly.
- JavaScript
- TypeScript
let heartbeat;
module.exports = {
async bootstrap({ strapi }) {
heartbeat = setInterval(() => {
strapi.log.debug('Heartbeat interval running');
}, 60_000);
},
async destroy() {
clearInterval(heartbeat);
},
};
let heartbeat: ReturnType<typeof setInterval>;
export default {
async bootstrap({ strapi }) {
heartbeat = setInterval(() => {
strapi.log.debug('Heartbeat interval running');
}, 60_000);
},
async destroy() {
clearInterval(heartbeat);
},
};
Usage
Combined usage
All 3 lifecycle functions can be put together to configure custom behavior during application startup and shutdown.
- Decide when your logic should run.
- Add initialization-only tasks (e.g. registering a custom field or provider) in
register(). - Add startup tasks that need full Strapi access (e.g. seeding or attaching webhooks) in
bootstrap(). - Add cleanup logic (e.g. closing external connections) in
destroy().
- Add initialization-only tasks (e.g. registering a custom field or provider) in
- Place the code in
src/index.js|ts. Keepregister()lean because it runs before Strapi is fully set up. - Restart Strapi to confirm each lifecycle executes in sequence.
let cronJobKey: string | undefined;
export default {
register({ strapi }) {
strapi.customFields.register({
name: 'color',
type: 'string',
plugin: 'color-picker',
});
},
async bootstrap({ strapi }) {
cronJobKey = 'log-reminders';
strapi.cron.add({
[cronJobKey]: {
rule: '0 */6 * * *', // every 6 hours
job: async () => {
strapi.log.info('Remember to review new content in the admin panel.');
},
},
});
},
async destroy({ strapi }) {
if (cronJobKey) {
strapi.cron.remove(cronJobKey);
}
},
};
You might find additional information in this blog article about registering lifecycle functions.