Skip to main content

Functions

Page summary:

src/index hosts global register, bootstrap, and destroy functions to run logic during application lifecycle.

The ./src/index.js file (or ./src/index.ts file in a TypeScript-based project) includes global register, bootstrap and destroy functions that can be used to add dynamic and logic-based configurations.

The functions can be synchronous, asynchronous, or return a promise.

Loading diagram...

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.

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');
}
};

Asynchronous

Asynchronous functions use the async keyword to await tasks such as API calls or database queries before Strapi continues.

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: {},
});
}
};

Returning a promise

Promise-returning functions hand back a promise so Strapi can wait for its resolution before continuing.

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);
});
}
};

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:

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.

module.exports = {
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:

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.

Tip

You can run yarn strapi console (or npm run strapi console) in the terminal and interact with the strapi object.

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');
}
},
};

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:

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.

let heartbeat;

module.exports = {
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.

  1. 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().
  2. Place the code in src/index.js|ts. Keep register() lean because it runs before Strapi is fully set up.
  3. Restart Strapi to confirm each lifecycle executes in sequence.
src/index.ts
let unsubscribeCreate: (() => void) | undefined;

export default {
register({ strapi }) {
strapi.customFields.register({
name: 'color',
type: 'string',
plugin: 'color-picker',
});
},

async bootstrap({ strapi }) {
unsubscribeCreate = strapi.eventHub.subscribe('entry.create', (event) => {
strapi.log.info(`New entry created in ${event.model}: ${event.result?.id}`);
});
},

async destroy() {
unsubscribeCreate?.();
},
};
Additional information

You might find additional information in this blog article about registering lifecycle functions.