When to use Strapi Lifecycle Hooks
After the update to Strapi v5, lifecycle hooks are no longer the recommended approach for most use cases. Instead, we recommend using Strapi's document service middleware.
You can read more about it in the Strapi Document Service Middleware.
If you are using Strapi v4, then lifecycle hooks are still the recommended approach. You can read more about it in the Strapi Lifecycle Hooks v4.
However, if you are using Strapi v5, you should use the document service middleware.
So, when should you use lifecycle hooks?
Lifecycle hooks are still useful for the following use cases:
- When you want to do something using the "users-permissions" plugin,
- When you want to do something using the "upload package",
In this blog post, we will examine an example of how to use lifecycle hooks to create a user profile when a user is created.
We will have two hooks:
afterCreate
: Create a user profile when a user is created.beforeDelete
: Delete the user profile when a user is deleted.
This will show you how to create and inject lifecycle hooks in Strapi.
Instead of building our project from scratch, we will use the following project: Strapi Lifecycle Hooks Example.
Setup
First, clone the repository:
git clone https://github.com/PaulBratslavsky/strapi-next-strapi-lifecycle-example
Then install the dependencies:
cd strapi-next-strapi-lifecycle-example
yarn setup
Then start both Strapi and Next.js projects by running the following command in the root of the project:
yarn dev
You should now be able to access your project at:
- http://localhost:3000 (Next.js project)
- http://localhost:1337 (Strapi project)
Navigate to the Strapi project and create your first Admin User.
Now that our project runs, let's review the quick demo and dive into the code.
You may notice that we are redirected to the Dashboard page after we log in and get a not found
error. That is because we have not created it yet.
The goal is to focus on Strapi lifecycle hooks and how to use them.
You notice that when you submit, we create a new user and a user profile for that user with additional information, such as fullName
and bio
.
Why Would We Want to Do This?
This approach helps separate a user's profile information from sensitive data, enhancing security and privacy.
You won't need to expose the user endpoint to the client.
Instead, you can use the me
endpoint to retrieve just the user's ID, which can then be used to fetch the user's profile information.
Alternatively, you can create a custom endpoint to fetch the user's profile information instead and completely lock down the user endpoint.
Let's jump into the code and see how we can achieve this.
Registering Lifecycle Hooks in Bootstrap Method
We will register the lifecycle hooks in the bootstrap
function in the src/index.ts
file.
You can learn more about the bootstrap method in the Strapi Bootstrap Method.
The bootstrap()
function is run before the back-end server starts but after the Strapi application has been setup, so you can access anything from the strapi object.
Take a look at the code in src/index.ts
, where we programmatically register the lifecycle hooks.
1bootstrap({ strapi }: { strapi: Core.Strapi }) {
2 // Registering a lifecycle subscriber for the users-permissions plugin
3 strapi.db.lifecycles.subscribe({
4 models: ["plugin::users-permissions.user"], // Applies only to users in users-permissions
5
6 /**
7 * Lifecycle hook triggered after a new user is created.
8 * Ensures that a user profile is created with either the provided full name and bio
9 * or a default generated username and bio if missing.
10 * @param {any} event - The event object containing the created user's details.
11 */
12 async afterCreate(event: any) {
13 const { result, params } = event;
14 const fullName = params?.data?.fullName || generateUsername();
15 const bio = params?.data?.bio || "No bio yet";
16 await createUserProfile(result.id, fullName, bio);
17 },
18
19 /**
20 * Lifecycle hook triggered before a user is deleted.
21 * Ensures that the associated user profile is also removed.
22 * @param {any} event - The event object containing the details of the user being deleted.
23 */
24 async beforeDelete(event: any) {
25 const { params } = event;
26 const idToDelete = params?.where?.id;
27 await deleteUserProfile(idToDelete);
28 },
29 });
30 },
This function registers a lifecycle subscriber in Strapi's database system to handle specific user creation and deletion actions.
After a user is created (afterCreate):
- It checks if the
fullName
andbio
fields are provided in the request. - If missing, it assigns a generated username and a default bio.
- It then creates a corresponding user profile using createUserProfile.
Before a user is deleted (beforeDelete):
- It retrieves the user ID that is about to be deleted.
- Calls deleteUserProfile to remove the associated profile.
This setup ensures that every user has a profile upon creation and that their profile is deleted when the user is removed.
We also have some helper functions to create and delete the user profile.
checkIfUserProfileExists
: Checks if a user profile already exists in the database for a given user ID.createUserProfile
: Creates a user profile for a user.deleteUserProfile
: Deletes a user profile for a user.generateUsername
: Generates a random username for a user if the fullName is not provided.
The full completed code looks like the following:
1// TODO: This only applies to the users-permissions plugin.
2// For anything else, please reference the following guide:
3// https://strapi.io/blog/what-are-document-service-middleware-and-what-happened-to-lifecycle-hooks-1
4
5import type { Core } from "@strapi/strapi";
6
7/**
8 * Generates a random username by combining an adjective, a noun, and a 4-digit number.
9 * @returns {string} A randomly generated username.
10 */
11function generateUsername(): string {
12 const adjectives = ["Swift", "Brave", "Clever", "Mighty", "Silent", "Witty", "Bold", "Eager"];
13 const nouns = ["Tiger", "Eagle", "Shark", "Wolf", "Falcon", "Panda", "Dragon", "Hawk"];
14 const randomNumber = Math.floor(1000 + Math.random() * 9000); // Generates a 4-digit random number
15
16 // Select a random adjective and noun from the arrays
17 const randomAdjective = adjectives[Math.floor(Math.random() * adjectives.length)];
18 const randomNoun = nouns[Math.floor(Math.random() * nouns.length)];
19
20 // Return the concatenated username
21 return `${randomAdjective}${randomNoun}${randomNumber}`;
22}
23
24/**
25 * Checks if a user profile already exists in the database for a given user ID.
26 * @param {string} userId - The ID of the user to check.
27 * @returns {Promise<any>} The user profile if found, otherwise an empty array.
28 */
29async function checkIfUserProfileExists(userId: string) {
30 console.log("FROM FIND USER PROFILE FUNCTION");
31 console.log("userId", userId);
32
33 // Query the database to find any existing user profile associated with the given user ID
34 const existingUserProfile = await strapi
35 .documents("api::user-profile.user-profile")
36 .findMany({
37 filters: {
38 user: {
39 id: {
40 $eq: userId, // Filter where the user ID matches
41 },
42 },
43 },
44 });
45
46 return existingUserProfile;
47}
48
49/**
50 * Creates a new user profile if one does not already exist.
51 * @param {string} userId - The ID of the user for whom the profile is being created.
52 * @param {string} fullName - The full name of the user.
53 * @param {string} bio - The user's bio.
54 */
55async function createUserProfile(userId: string, fullName: string, bio: string) {
56 const userProfile = await checkIfUserProfileExists(userId);
57
58 // If a profile already exists, log a message and return without creating a new one
59 if (userProfile.length > 0) {
60 console.log("USER PROFILE ALREADY EXISTS");
61 return;
62 }
63
64 // Create a new user profile with the provided user ID, full name, and bio
65 await strapi.documents("api::user-profile.user-profile").create({
66 data: {
67 user: userId,
68 fullName: fullName,
69 bio: bio,
70 },
71 });
72}
73
74/**
75 * Deletes a user's profile if it exists.
76 * @param {string} userId - The ID of the user whose profile should be deleted.
77 */
78async function deleteUserProfile(userId: string) {
79 const userProfile = await checkIfUserProfileExists(userId);
80
81 // If a profile exists, delete it from the database
82 if (userProfile.length > 0) {
83 await strapi.documents("api::user-profile.user-profile").delete({
84 documentId: userProfile[0].documentId, // Use the document ID of the first found profile
85 });
86 }
87}
88
89export default {
90 /**
91 * An asynchronous register function that runs before
92 * your application is initialized.
93 *
94 * This gives you an opportunity to extend code.
95 */
96 register(/* { strapi }: { strapi: Core.Strapi } */) {},
97
98 /**
99 * An asynchronous bootstrap function that runs before
100 * your application gets started.
101 *
102 * This gives you an opportunity to set up your data model,
103 * run jobs, or perform some special logic.
104 */
105 bootstrap({ strapi }: { strapi: Core.Strapi }) {
106 // Registering a lifecycle subscriber for the users-permissions plugin
107 strapi.db.lifecycles.subscribe({
108 models: ["plugin::users-permissions.user"], // Applies only to users in users-permissions
109
110 /**
111 * Lifecycle hook triggered after a new user is created.
112 * Ensures that a user profile is created with either the provided full name and bio
113 * or a default generated username and bio if missing.
114 * @param {any} event - The event object containing the created user's details.
115 */
116 async afterCreate(event: any) {
117 const { result, params } = event;
118 const fullName = params?.data?.fullName || generateUsername();
119 const bio = params?.data?.bio || "No bio yet";
120 await createUserProfile(result.id, fullName, bio);
121 },
122
123 /**
124 * Lifecycle hook triggered before a user is deleted.
125 * Ensures that the associated user profile is also removed.
126 * @param {any} event - The event object containing the details of the user being deleted.
127 */
128 async beforeDelete(event: any) {
129 const { params } = event;
130 const idToDelete = params?.where?.id;
131 await deleteUserProfile(idToDelete);
132 },
133 });
134 },
135};
The last piece of the puzzle is to ensure that we can pass additional fields on the user creation.
To allow extra fields like fullName
and bio
, we need to update the config/plugins.ts
file with the following:
1export default () => ({
2 "users-permissions": {
3 config: {
4 register: {
5 allowedFields: ["fullName", "bio"],
6 },
7 },
8 },
9});
This will allow us to pass the fullName
and bio
and access them in the afterCreate
hook.
Conclusion
In this blog post we learned how to use Strapi lifecycle hooks to create a user profile when a user is created.
We also learned how to use the bootstrap
method to register the lifecycle hooks.
We also learned how to use the config/plugins.ts
file to allow extra fields to be passed on to the user creation.
I hope you enjoyed this blog post. If you have any questions, please leave a comment below.
Github Project Repo
You can find the complete code for this project in the following Github repo.
Strapi Open Office Hours
If you have any questions about Strapi 5 or just would like to stop by and say hi, you can join us at Strapi's Discord Open Office Hours Monday through Friday at 12:30 pm - 1:30 pm CST: Strapi Discord Open Office Hours