Lifecycle Callbacks from generated APIs and plugins are currently very broken (different levels depending on the database) and the Strapi team do plan to fix these over the next few weeks (likely before the stable release in Q2 2020).
Usage of them right now is not recommended, some will work, others will not. There is currently ongoing work to finish up some pre-requisites that will be needed to fix the Lifecycles.
This issue will serve as a central place for all the issues to be linked to and discussed for when they are ready to be worked on.
Informations
What is the current behavior?
When updating an existing entry, the before/afterSave lifecycle callbacks are not being called. Which is contrary to the comment/description of the function which states that it should be called on an 'insert' and an 'update'. The before/afterUpdate is also getting called for not only on 'update', but also on 'insert' queries.
Steps to reproduce the problem
What is the expected behavior?
// After saving a value.
// Fired after an insert or update query.
Suggested solutions
Still digging into a solution, but having gone far enough into the code to get into what triggers the model lifecycle callbacks.
@MattAurich can you test this with MySQL and/or Postgres as well. I'm wondering if there is an issue with mongoose or bookshelf
Hello @MattAurich I just tried with laster version of Strapi and It works.


What look like you model please ?

My model, though it is freshly generated with just some console logs. I updated strapi to the latest version (3.0.0-alpha.12.6) to generate the above response.
'use strict';
/**
* Lifecycle callbacks for the `Stuff` model.
*/
module.exports = {
// Before saving a value.
// Fired before an `insert` or `update` query.
beforeSave: async (model) => {
console.log('beforeSave: ');
},
// After saving a value.
// Fired after an `insert` or `update` query.
afterSave: async (model, result) => {
console.log('afterSave: ');
},
// Before fetching all values.
// Fired before a `fetchAll` operation.
beforeFetchAll: async (model) => {
console.log('beforeFetchAll: ');
},
// After fetching all values.
// Fired after a `fetchAll` operation.
afterFetchAll: async (model, results) => {
console.log('afterFetchAll: ');
},
// Fired before a `fetch` operation.
beforeFetch: async (model) => {
console.log('beforeFetch: ');
},
// After fetching a value.
// Fired after a `fetch` operation.
afterFetch: async (model, result) => {
console.log('afterFetch: ');
},
// Before creating a value.
// Fired before an `insert` query.
beforeCreate: async (model) => {
console.log('beforeCreate: ');
},
// After creating a value.
// Fired after an `insert` query.
afterCreate: async (model, result) => {
console.log('afterCreate: ');
},
// Before updating a value.
// Fired before an `update` query.
beforeUpdate: async (model) => {
console.log('beforeUpdate: ');
},
// After updating a value.
// Fired after an `update` query.
afterUpdate: async (model, result) => {
console.log('afterUpdate: ');
},
// Before destroying a value.
// Fired before a `delete` query.
beforeDestroy: async (model) => {
console.log('beforeDestroy: ');
},
// After destroying a value.
// Fired after a `delete` query.
afterDestroy: async (model, result) => {
console.log('afterDestroy: ');
}
};
From my understanding of the comments in the model file, the generated output for an 'update' should be:
beforeSave:
afterSave:
beforeFetch:
afterFetch:
beforeUpdate:
afterUpdate:
beforeFetch:
afterFetch:
beforeFetch:
afterFetch:
And the generated output for a 'create' should be:
beforeCreate:
afterCreate:
beforeSave:
afterSave:
beforeFetch:
afterFetch:
beforeFetch:
afterFetch:
beforeFetch:
afterFetch:
**Or at least that is my understanding from the documentation, I am slowly getting familiar with the code base.
Please update to alpha.12.6 to have save fire on update.
About update on create action, it's because the content manager create then entry data and then update for relations data.
Let me know if it works with alpha.12.6
I did a fresh install of alpha.12.6 after doing an uninstall of strapi, then created a brand new project for the above example. You've tried it with a PUT request?
Interesting, about the update action being how strapi works for relations data, so does it run across every object in the model? (Just for clarity, it can't be the actual 'Content Manager' plugin, as it still does it on curl request or if the plugin is removed.)
I just come across with similar problem and I have updated strapi to alpha.12.6 but neither beforeSave nor afterSave is fired.
Informations
Steps to reproduce the problem


It's not related to Strapi. Please check https://github.com/Automattic/mongoose/issues/419
If beforeSave/afterSave aren't fired on update but only on insert then means that using beforeSave/afterSave is the same as using beforeCreate/afterCreate. In this case I don't really understand why do we need both.
Otherwise means that the following comment it's wrong or at least misleading:
// Fired before an
insertorupdatequery.
this is somehow related to Strapi.
Just installed my 1st strapi app to test it out and was quite confused about the callbacks.
*Update is triggered.*Save is not triggered.*Destroy is not triggered.*Destroy is not triggered, however *Update is. Surely this can't be intended?

Node.js version: 10.11.0
npm version: 5.7.1
Strapi version: 3.0.0-alpha.14.5
Database: MongoDB
Operating system: Windows 10 Pro x64 v1803
@lauriejim : About
updateoncreateaction, it's because the content managercreatethen entry data and thenupdatefor relations data.
Guess that answers by first questions, however, what's the use for save then, if update is ran on creation and update?
On a related note, the afterUpdate: async (model, result) => {} callback does not expose any information about the item that was updated. This is what the result object looks like on Strapi v3.0.0-alpha.15:

Would you mind to check _update object inside model? You may find something useful.
@henrych4 - thanks for your comment. I did come across the _update object, and while it does contain the atomic properties changed, it does not seem to contain any of the identifiers (like the id attribute) that I can use to grab the entire object.
The id can be found in _conditions object.
In 3.0.0-alpha.22, before/afterUpdate lifecycle methods do not fire when updating a value using the content manager or using a PUT request.
before/afterDestroy do not fire when deleting using the UI, but they work when sending a DELETE request.
Very frustrating when trying to implement a websocket model to sync with downstream systems as there is no data consistency anymore.
I can confirm that this is still an issue in 3.0.0-alpha.24.1
I think that we have identified the issue here.
This pull request here is what broke this functionality:
https://github.com/strapi/strapi/pull/2682
So, now in the connector, in here:
const preLifecycle = {
validate: 'beforeCreate',
findOneAndUpdate: 'beforeUpdate',
findOneAndRemove: 'beforeDestroy',
remove: 'beforeDestroy',
update: 'beforeUpdate',
find: 'beforeFetchAll',
findOne: 'beforeFetch',
save: 'beforeSave'
};
There should also be a updateOne there, because after this pull request the mongoose hook slightly changes!!
beforeUpdate/afterUpdate should be triggered after the fix in #2905. Upgrade strapi to latest version and it should work as expected.
However, lifecycle methods related to save and destroy are not fired. The problem exists since very earlier version of strapi.
Are lifecicle events working on 3.0.0-beta.5/6 ??
When i call in a given controller this code:
const release = await strapi.services.release.create(relParams);
code on api/release/models/Release.js, method beforeSave is not being called; what i'm missing ?
Hello! I just tried to implement your service call in a custom controller function.
And the Release lifeCycle function is called.
I don't know how you implement it but it works.
@lauriejim i've found beforeSave is broken! I've replaced with beforeCreate and beforeUpdate
@lauriejim also i 've found beforeUpdateis called twice, first call model has a Query with a _update field containing new values; later second call the _update field contains an empty object
this is still an issue in beta.11.
beforeSave/afterSave don't fire for PUT requests and beforeUpdate/afterUpdate fire twice
We have to plug life cycle functions to the core queries instead of native Models life cycle functions.
Would love for this to get improved. Have enjoyed utilising the lifecycle methods but as everyone's saying here, it's really difficult to make use of them properly if the lifecycle can only cover half of the CRUD operations.
In my case the beforeUpdate and afterUpdate fired twice. Found a little bit of hope in the save lifecycle as a way to bypass the update firing but haven't been able to correctly trigger the destroy hooks. In my case I'm using hooks to mirror product changes into Stripe and lack of delete leaves a lot of junk that has to be manually cleaned up whenever the dataset changes a whole bunch.
For me afterUpdate fired twice too on beta.16.1
Additional bug related to lifecycles:
When using model.set() to modify a model, the altered model is not stored in the database during update-related callbacks, but is stored successfully during insert-related callbacks.
Examples:
// Works as expected and the modified model is stored in the DB.
beforeCreate: async (model, attrs, options) => {
console.log('model before:', model);
model.set('foo', 'modifying on insert sets the value successfully!');
console.log('model after:', model); // this is stored in DB
},
// BROKEN: The modified model logs successfully, but is not stored in the DB;
// the UNmodified model is stored in the DB.
beforeUpdate: async (model, attrs, options) => {
console.log('model before:', model); // Stored in DB
model.set('foo', 'does not update this value');
console.log('model after:', model); // Not stored in DB, but should be. Bug!
},
Note that when using the beforeSave() callback, similar behavior occurs--i.e. if it's an insert, the modified model is stored in the DB; if it's an update, the modified model is NOT stored and the UNmodified model is stored.
Additional bug related to lifecycles:
When using
model.set()to modify a model, the altered model is not stored in the databse during updated-related callbacks, but is stored successfully during insert-related callbacks.Examples:
// Works as expected and the modified model is stored in the DB. beforeCreate: async (model, attrs, options) => { console.log('model before:', model); model.set('foo', 'modifying on insert sets the value successfully!'); console.log('model after:', model); // this is stored in DB }, // BROKEN: The modified model logs successfully, but is not stored in the DB; // the UNmodified model is stored in the DB. beforeUpdate: async (model, attrs, options) => { console.log('model before:', model); // Stored in DB model.set('foo', 'does not update this value'); console.log('model after:', model); // Not stored in DB, but should be. Bug! },Note that when using the
beforeSave()callback, similar behavior occurs--i.e. if it's an insert, the modified model is stored in the DB; if it's an update, the modified model is NOT stored and the UNmodified model is stored.
i have the same problem and need to save the modified model, any solutions?
@mcaulirealt for now I wouldn't recommend using Lifecycle callbacks as they are effectively broken until the new DBAL layer is done.
@mcaulirealt No solution yet until lifecycles callbacks are fixed.
@websocket98765 have you tried using setTimeout inside the function with a 100 or 1000 ms before executing the code?
That worked for me in the afterCreate function on Strapi 3.0.0-beta.16.8
Any updates on this topic or a temporary alternative to model.set with beforeUpdate or beforeSave?
_(Using PUT method)_
I have updated the original post and am linking all other lifecycle callback issues here.
Thank you for that @derrickmehaffy.
There are any ETA to fix this? Thanks in advance!
@abdonrd no updated ETA yet. I think the DBAL layer is coming here soon (beta.18 I think?) but it's just the core stuff. However after it initially gets added I think @alexandrebodin will likely start poking at this a bit more. It's a fairly complex fix that almost needs a rewrite since the callbacks will change from being dependent on the upstream hook packages (mongoose/bookshelf) to the dbal layer.
Is there any WIP available? Would be useful since we are thinking of fixing the missing relations on beforeSave actions while waiting for full rework.
As temporary workaround of not working model.set() in beforeSave method, I define a callback outside of beforeSave scope and execute it asynchronously in afterSave. And then I reset the callback.
E.g.
let postProcessAction = null;
module.exports = {
beforeSave: async (model) => {
let fieldToUpdate = {};
if (checkIfPostProcessIsNeeded(model)) {
fieldToUpdate = { foo: 'Bar' };
}
if (Object.keys(fieldToUpdate).length !== 0) {
postProcessAction = () => strapi.query('model').update({ id: model.attributes.id }, fieldToUpdate);
}
},
afterSave: async () => {
if (postProcessAction) {
postProcessAction().catch((error) => {
console.log('Error', error);
});
postProcessAction = null;
}
}
};
Note:
You can't execute postProcessAction with await since an entity is in a transaction and is locked for an update in afterSave event
@emukans with your temporary workaround were you able to have the response update within the same query. For example, I used your workaround and it'll save the points value and update the score (updated in the beforeUpdate lifecycle) in an updateUser query like:
mutation updateUser ($id: ID! ) {
updateUser(input: {
where: {
id: $id
},
data: {
points: 1383
}
}), {
user {
id
email
score
points
}
}
}
The score in the response is the old score (but the score is updated in the db as the new score).
Hi @jc111t,
No with the workaround above you won't see updates in the same query since the actual update happens asynchronously after the update. In my case, the update query returns just success/fail and error message if there some. Maybe you can revise your logic to something similar until life cycle events are reviewed.
Can anyone suggest a workaround to:
afterCreate() being called twice when using Groups Field #4166.
@therobster121
https://portal.productboard.com/strapi/1-public-roadmap/c/27-webhooks
ETA is Q4 2019 though it might be pushed to Q1 2020
Thanks @emukans - your solution is definitely helpful but I think the whole splitting before/after is unnecessary unless I am missing something?
This worked for me:
// api/blog/models/Blog.js
const slug = require('slug');
module.exports = {
afterSave: async (model, attrs, options) => {
const title = model.attributes.Title || model.attributes.title || null;
if (title) {
strapi.query('blog').update({ id: model.attributes.id }, { slug: slug(title, { lower: true }) });
}
},
};
Edit: Annoyingly to do it on creation it uses a very different method. I assume from above chat that future bugfixes to the main repo will iron this out?
beforeCreate: async (model, attrs, options) => {
const title = model.attributes.Title || model.attributes.title || null;
if (title) {
model.set('slug', slug(title, { lower: true }));
}
},
@lauriejim Webhooks has arrived and I assume that for now this is the way to handle changes (requires adding webhook and controller). Which is fine, and working properly like after Create/Update/Delete callbacks, which handles most cases, but what with before?
Do you plan review of callbacks any time soon?
I'm asking because have a deadline comming and wonder if I should go with webhooks or wait for callbacks.
@lauriejim Webhooks has arrived and I assume that for now this is the way to handle changes (requires adding webhook and controller). Which is fine (...)
I don't agree. Using webhooks to call ourselves just to do something after creating/updating/deleting an entry is a real overkill. It would probably work, but it's a dirty hack.
We want to do this before april 2020 but no exact deadline for it right now :/
@sarneeh You're right. It's hacky, it's dirty. But work need to be done so I need to use something that works, unlike lifecycle methods.
@alexandrebodin thanks. Looking forward for that :)
BTW, if someone wants to contribute and help us work on that please let me know on slack ;)
@sarneeh You're right. It's hacky, it's dirty. But work need to be done so I need to use something that works, unlike lifecycle methods.
You can override the content-manager plugin and hook into create/update/delete/... there 馃槂 Like this:
extensions/content-manager/services/ContentManager.js
const OriginalContentManager = require('strapi-plugin-content-manager/services/ContentManager')
module.exports = {
async create(...args) {
// ...your logic
return OriginalContentManager.create(...args)
},
async edit(...args) {
// ...your logic
return OriginalContentManager.edit(...args)
},
async delete(...args) {
// ...your logic
return OriginalContentManager.delete(...args)
}
}
But this is not per-model. You'd need to identify your model by yourself (from the args).
Thank you for giving us Webhooks within Strapi.
Does someone know if webhooks between Strapi and Gatsby work locally? If so, is there some payload I should pass through? Does anyone have a link of how to do this?
I am following this guide: https://strapi.io/documentation/3.0.0-beta.x/concepts/webhooks.html#what-is-a-webhook and I am having trouble updating (rebuilding) my Gatsby data once I update some entry in Strapi.
P.S. I am working locally using Postgres DB
Thank you for giving us Webhooks within Strapi.
Does someone know if webhooks between Strapi and Gatsby work locally? If so, is there some payload I should pass through? Does anyone have a link of how to do this?
I am following this guide: https://strapi.io/documentation/3.0.0-beta.x/concepts/webhooks.html#what-is-a-webhook and I am having trouble updating (rebuilding) my Gatsby data once I update some entry in Strapi.
P.S. I am working locally using Postgres DB
@DekiGK Webhooks are HTTP requests, you'd need a server locally which has an endpoint that triggers a rebuild for your Gatsby website. Then you can attach this into a Strapi webhook.
@DekiGk Webhooks are HTTP requests, you'd need a server locally which has an endpoint that triggers a rebuild for your Gatsby website. Then you can attach this into a Strapi webhook.
I know this as I have seen the previous workaround.
Unfortunately it took a bit more of digging until I made it work and I am putting this here for whoever stumbles upon the same issue. For Gatsby there is an ENV variable you can add to expose the endpoint that is needed to update GraphQL data. ENABLE_GATSBY_REFRESH_ENDPOINT=1 should be added to your .env file and then you should just trigger a POST request (where Strapi webhooks become useful) to http://localhost:8000/__refresh and that should work.
More details can be found here:
https://github.com/gatsbyjs/gatsby/issues/14395#issuecomment-496977070
https://www.gatsbyjs.org/docs/environment-variables/#reserved-environment-variables
@DekiGk Please read the comments in the issue you've mentioned. This method apparently does not work in Gatsby.
@sarneeh It works locally for me. Haven't tested staging and production yet, but seems to work as expected.
Hi, for staging and production the rebuild will need to be triggger via your deploy tool (e.g netlify, gitlab etc)
I am subscribed here in anticipation of lifecycle calkbacks getting fixed. I don't think webhooks provide an adequate workaround and would appreciate if you move discussions unrelated to lifecycle callbacks to separate issues or slack.
Is there an item where we can track the progress on this issue in ProductBoard?
This is a big one for us too, really looking forward to resolution.
Unfortunately for us, this is a dealbreaker. We need to geocode address data, so we initially set out to create a custom field type that used Google Places Autocomplete. That approach turned out to be a dead end because there are no front end customization hooks. Then we looked at these lifecycle hooks in hopes of using Google Maps API. Such a bummer.
Edit: I actually found a decent workaround for our needs. In our case, we're using Postgres so I was able to access bookshelf/knex and avoid triggering an infinite loop of lifecycle callbacks. Where my model name is "test" and the field I want to update with every insert/update...
afterSave: async (model, response, options) => {
const Test = strapi.query('test').model;
Test.query(q => {
q.where('id', model.attributes.id).update({
Data: {test: 3}
}).catch(e => {
console.log(e);
});
});
}
@Elementrat no it's not on the product board as that is for features. This issue is the tracking one.
It looks like that M2M relations are not respected in the args passed to the callbacks.
model settings:

callback on that model:

log output -> "products" is missing:

There is an inconsistency in the life cycle callbacks.
beforeCreate, afterCreate, beforeSave and afterSave return a Model object while others return a Query object.
beforeSave: async (model) => {
console.log('beforeSave: ', model.constructor.name);
}
Result:

@derrickmehaffy as Usage of this right now is not recommended, so how can I customise update method that is called from strapi admin entity service, it seems it is generalized service(strapi.entityService) & if we write custom update service in Model's service it won't get called from admin UI, I want create password hash before saving my custom User model, is it possible to do without using this life cycle method?
@derrickmehaffy as Usage of this right now is not recommended, so how can I customise update method that is called from strapi admin entity service, it seems it is generalized service(strapi.entityService) & if we write custom update service in Model's service it won't get called from admin UI, I want create password hash before saving my custom User model, is it possible to do without using this life cycle method?
For now, you will need to override the controllers in the content-manager plugin:
Hello I am new to strapi, and I stumbled upon this issue while trying to implement afterSave hook to send my saved model to algolia search. the afterSavehook is fired 3 times and only id 's are fetched on my related models.
@derrickmehaffy I am interested in your solution is there any documentation about overriding plugins ?
Hello I am new to strapi, and I stumbled upon this issue while trying to implement
afterSavehook to send my saved model to algolia search. theafterSavehook is fired 3 times and only id 's are fetched on my related models.@derrickmehaffy I am interested in your solution is there any documentation about overriding plugins ?
@ayhid
https://strapi.io/documentation/3.0.0-beta.x/getting-started/troubleshooting.html#how-do-i-customize-a-plugin
@derrickmehaffy thanks a lot I managed to override the plugin & do things as I want. It's a little bit clunky but It will do for now.
Hi, Any updates on this issue: https://github.com/strapi/strapi/issues/3863
Hi, Any updates on this issue: #3863
As this issue suggests @ashbuilds LC callbacks are currently broken so the above workarounds should be used instead. For the moment there is no news other than this issue will be fixed before stable.
It's not an easy issue to fix directly as previously these callbacks were based on the ORMs themselves, in which they are failing because those ORMs have removed many options so the LC CBs have to be moved to Strapi logic instead. After the few remaining features are added, I believe the Strapi team plan to dedicate quite a bit of time into "bug fixes" (they have already been doing that for critical/high priority ones, this not being either) before stable.
@derrickmehaffy I totally understand, Thanks for your quick response. Could you please tell me the possible release date for stable version?
And thanks again. I appreciate what Strapi has done 鉂わ笍
@derrickmehaffy I totally understand, Thanks for your quick response. Could you please tell me the possible release date for stable version?
And thanks again. I appreciate what Strapi has done 鉂わ笍
There is no set date for that yet
Thanks 馃檪
For now I'm just using some extra fields to solve this issue specially for beforeSave. Later refactor the code to use LC once this is fixed.
Have a nice day.

New draft PR! https://github.com/strapi/strapi/pull/5927
This has been fixed in rc.0 and there is currently no plans to backport it to the beta versions. Migration to the rc tagged releases is not currently recommended but the stable release is expected by the end of this month (may 2020).
For now I am marking this as closed.
Life cycle callbacks are still not working in stable version 3.0.1
@TheHelias There was some breaking changes in lifecycle methods. I tested them and all works fine.
Remember that new lifecycle methods must be inside lifecycles object inside model file, like so:
module.exports = {
lifecycles: {
async beforeCreate(data) {
// do sth with data
},
},
};
Here's more examples: https://strapi.io/documentation/v3.x/concepts/models.html#lifecycle-hooks
If you encountered some problems with lifecycle methods, could You please provide more information?
It works now. Didn't notice this (Remember that new lifecycle methods must me inside lifecycles object inside model file, like so:) earlier. Good job!
@derrickmehaffy
@TheHelias
or anyone :)
How do you call a lifecycle hook manually (using Mongo)?
strapi.query(modelName).model.lifecycles is undefined, so the Bookshelf way doesn't work.
@derrickmehaffy
@TheHelias
or anyone :)How do you call a lifecycle hook manually (using Mongo)?
strapi.query(modelName).model.lifecycles is undefined, so the Bookshelf way doesn't work.
You can't directly call it manually (as of yet). But if you call the normal strapi.query('model').action() such as find, findOne, create, update, or delete. The lifecycles will trigger. Lifecycles are attached to those actions.
I'm going to lock this thread as the lifecycles have been fixed and any further discussion will be off topic. Any questions should be directed towards the discussions
Most helpful comment
I am subscribed here in anticipation of lifecycle calkbacks getting fixed. I don't think webhooks provide an adequate workaround and would appreciate if you move discussions unrelated to lifecycle callbacks to separate issues or slack.