Hooks
Hooks are events that you can listen to, and which are triggerred when their corresponding method is called.
They are useful for adding custom functionality to the core of the application.
For example, if you want to always set a value on a model before saving it, you can listen to the Model beforeUpdate hook.
Static Sequelize Hooks
The Sequelize class supports the following hooks:
| Hook name | Async | Run when | 
|---|---|---|
beforeInit, afterInit | ❌ | A sequelize instance is created | 
Example:
import { Sequelize } from '@sequelize/core';
Sequelize.hooks.addListener('beforeInit', () => {
  console.log('A new sequelize instance is being created');
});
Instance Sequelize Hooks
Sequelize instances support the following hooks:
| Hook name | Async | Run when | 
|---|---|---|
beforeDefine, afterDefine | ❌ | A new Model class is being registered | 
beforeQuery, afterQuery | ✅ | A SQL query is being run | 
beforeBulkSync, afterBulkSync | ✅ | sequelize.sync is called | 
beforeConnect, afterConnect | ✅ | Whenever a new connection to the database is being created | 
beforeDisconnect, afterDisconnect | ✅ | Whenever a connection to the database is being closed | 
beforePoolAcquire, afterPoolAcquire | ✅ | Whenever a new connection from pool is being acquired | 
All Model hooks are also fired on the sequelize instance:
import { Sequelize } from '@sequelize/core';
const sequelize = new Sequelize(/* options */);
// This will be called whenever findAll is called on any model.
sequelize.hooks.addListener('beforeFind', () => {
  console.log('findAll has been called a model');
});
Registering Sequelize hooks
Instance Sequelize hooks can be registered in two ways:
- 
Using the
hooksproperty of theSequelizeinstance:import { Sequelize } from '@sequelize/core';
const sequelize = new Sequelize(/* options */);
sequelize.hooks.addListener('beforeDefine', () => {
console.log('A new Model is being initialized');
}); - 
Through the
Sequelizeoptions:import { Sequelize } from '@sequelize/core';
const sequelize = new Sequelize({
/* options */
hooks: {
beforeDefine: () => {
console.log('A new Model is being initialized');
},
},
}); 
Model Sequelize Hooks
Model classes support the following hooks:
| Hook name | Async | Run when | 
|---|---|---|
beforeAssociate, afterAssociate | ❌ | Whenever an association is declared on the model | 
beforeSync, afterSync | ✅ | When sequelize.sync or Model.sync are called | 
beforeValidate, afterValidate, validationFailed | ✅ | When the model's attributes are being validated (happens in most model methods) | 
beforeFind, beforeFindAfterExpandIncludeAll, beforeFindAfterOptions, afterFind | ✅ | When Model.findAll is called1 | 
beforeCount | ✅ | When Model.count is called | 
beforeUpsert, afterUpsert | ✅ | When Model.upsert is called | 
beforeBulkCreate, afterBulkCreate | ✅ | When Model.bulkCreate is called | 
beforeBulkDestroy, afterBulkDestroy | ✅ | When Model.destroy is called | 
beforeDestroy, afterDestroy | ✅ | When Model#destroy is called | 
beforeBulkRestore, afterBulkRestore | ✅ | When Model.restore is called | 
beforeRestore, afterRestore | ✅ | When Model#restore is called | 
beforeBulkUpdate, afterBulkUpdate | ✅ | When Model.update is called | 
beforeUpdate, afterUpdate | ✅ | When Model#update or Model#save is called (and the model isn't new) | 
beforeCreate, afterCreate | ✅ | When Model#save is called (and the model is new) | 
beforeSave, afterSave | ✅ | When Model#update or Model#save is called | 
Registering Model hooks
Instance Sequelize hooks can be registered in three ways:
- 
Using the
hooksproperty of theSequelizeinstance:import { Sequelize, DataTypes } from '@sequelize/core';
const sequelize = new Sequelize(/* options */);
const MyModel = sequelize.define('MyModel', {
name: DataTypes.STRING,
});
MyModel.hooks.addListener('beforeFind', () => {
console.log('findAll has been called on MyModel');
}); - 
Through the options of
Model.init, orsequelize.define:import { DataTypes } from '@sequelize/core';
const MyModel = sequelize.define(
'MyModel',
{
name: DataTypes.STRING,
},
{
hooks: {
beforeFind: () => {
console.log('findAll has been called on MyModel');
},
},
},
); - 
Using decorators.
There is a decorator for every model hook, their names are the same as the hook names, but inPascalCase.infoSequelize currently only supports the legacy/experimental decorator format. Support for the new decorator format will be added in a future release.
All decorators must be imported from
@sequelize/core/decorators-legacy:import { Attribute, Table } from '@sequelize/core/decorators-legacy';Using legacy decorators requires to use a transpiler such as TypeScript, Babel or others to compile them to JavaScript. Alternatively, Sequelize also supports a legacy approach that does not require using decorators, but this is discouraged.
If you're using TypeScript, you will also need to configure it to be able to resolve this export, see our Getting Started guide for more information.
import { Sequelize, Model, Hook } from '@sequelize/core';
import { BeforeFind } from '@sequelize/core/decorators-legacy';
export class MyModel extends Model {
@BeforeFind
static logFindAll() {
console.log('findAll has been called on MyModel');
}
} 
Sync vs Async Hooks
In the tables above, you can see that some hooks are marked as Async.
This means that your listener can return a Promise that will be awaited before continuing with the execution of the hook.
Be careful about doing async operations in hooks. Hooks are run in series and will not run the next hook until your hook has resolved. This can cause performance issues if you have a lot of hooks.
Trying to return a Promise from a synchronous hook will raise an error.
Removing hooks
You can remove hooks using hooks.removeListener and providing either the same callback as hooks.addListener or the name of your listener:
class Book extends Model {}
Book.init(
  {
    title: DataTypes.STRING,
  },
  { sequelize },
);
const myListener = (book, options) => {
  // ...
};
Book.hooks.addListener('afterCreate', 'yourHookIdentifier', myListener);
// Both of these will remove the hook:
Book.hooks.removeListener('afterCreate', myListener);
Book.hooks.removeListener('afterCreate', 'yourHookIdentifier');
Hooks added by decorators cannot be removed using the callback instance, but can still be removed if they are named:
import { Sequelize, Model, Hook } from '@sequelize/core';
import { BeforeFind } from '@sequelize/core/decorators-legacy';
export class MyModel extends Model {
  @BeforeFind({ name: 'yourHookIdentifier' })
  static logFindAll() {
    console.log('findAll has been called on MyModel');
  }
}
// This will not work
MyModel.hooks.removeListener('beforeFind', MyModel.logFindAll);
// But this will
MyModel.hooks.removeListener('beforeFind', 'yourHookIdentifier');
However, we do not recommend removing hooks added through decorators, as it may make your code harder to understand.
Associations Method Hooks
Methods added by Associations on your model do provide hooks specific to them, but they are built on top of regular model methods, which will trigger hooks.
For instance, using the add / set mixin methods will trigger the beforeUpdate and afterUpdate hooks.
individualHooks
Using this option is discouraged for two reasons:
- Unlike the "normal" hook, the data provided to events emitted by 
individualHookscannot be modified. Only the "bulk" event's data can be modified. - This option is slow, as it will need to fetch the instances that need to be destroyed.
 
Use with care. If you need to react to database changes, consider using your database's triggers and notification system instead.
When using static model methods such as Model.destroy or Model.update, only their corresponding "bulk" hook (such as beforeBulkDestroy) will be called, not the instance hooks (such as beforeDestroy).
If you want to trigger the instance hooks, you use the individualHooks option to the method to run the instance hooks for each row that will be impacted.
The following example will trigger the beforeDestroy and afterDestroy hooks for each row that will be deleted:
User.destroy({
  where: {
    id: [1, 2, 3],
  },
  individualHooks: true,
});
Exceptions
Only Model methods trigger hooks. This means there are a number of cases where Sequelize will interact with the database without triggering hooks. These include but are not limited to:
- Instances being deleted by the database because of an 
ON DELETE CASCADEconstraint, except if thehooksoption is true. - Instances being updated by the database because of a 
SET NULLorSET DEFAULTconstraint. - Raw queries.
 - All QueryInterface methods.
 
If you need to react to these events, consider using your database's native and notification system instead.
Hooks for cascade deletes
As indicated in Exceptions, Sequelize will not trigger hooks when instances are deleted by the database because of an ON DELETE CASCADE constraint.
However, if you set the hooks option to true when defining your association, Sequelize will trigger the beforeDestroy and afterDestroy hooks for the deleted instances.
Using this option is discouraged for the following reasons:
- This option requires many extra queries. The 
destroymethod normally executes a single query. If this option is enabled, an extraSELECTquery, as well as an extraDELETEquery for each row returned by the select will be executed. - If you do not run this query in a transaction, and an error occurs, you may end up with some rows deleted and some not deleted.
 - This option only works when the instance version of 
destroyis used. The static version will not trigger the hooks, even withindividualHooks. - This option will not work in 
paranoidmode. - This option will not work if you only define the association on the model that owns the foreign key. You need to define the reverse association as well.
 
This option is considered legacy. We highly recommend using your database's triggers and notification system if you need to be notified of database changes.
Here is an example of how to use this option:
import { Model } from '@sequelize/core';
import { HasMany, BeforeDestroy } from '@sequelize/core/decorators-legacy';
class User extends Model {
  // This "hooks" option will cause the "beforeDestroy" and "afterDestroy"
  @HasMany(() => Post, { hooks: true })
  declare posts: Post[];
}
class Post extends Model {
  @BeforeDestroy
  static logDestroy() {
    console.log('Post has been destroyed');
  }
}
const sequelize = new Sequelize({
  /* options */
  models: [User, Post],
});
await sequelize.sync({ force: true });
const user = await User.create();
const post = await Post.create({ userId: user.id });
// this will log "Post has been destroyed"
await user.destroy();
Hooks and Transactions
Many model operations in Sequelize support specifying a transaction in the options parameter of the method.
If a transaction is specified in the original call, it will be present in the options parameter passed to the hook function.
For example, consider the following snippet:
User.hooks.addListener('afterCreate', async (user, options) => {
  // We can use `options.transaction` to perform some other call
  // using the same transaction of the call that triggered this hook
  await User.update(
    { mood: 'sad' },
    {
      where: {
        id: user.id,
      },
      transaction: options.transaction,
    },
  );
});
await sequelize.transaction(async transaction => {
  await User.create(
    {
      username: 'someguy',
      mood: 'happy',
    },
    {
      transaction,
    },
  );
});
If we had not included the transaction option in our call to User.update in the preceding code,
no change would have occurred, since our newly created user does not exist in the database until the pending transaction
has been committed.
Footnotes
- 
findAll: Note that some methods, such as
Model.findOne,Model.findAndCountAlland association getters will also callModel.findAllinternally. This will cause thebeforeFindhook to be called for these methods too. ↩