LogoZonai
zonai.dev

Update Hooks

beforeUpdate, afterUpdateSuccess, and afterUpdateError extension hooks.

Hook Signatures#

Future<void> beforeUpdate(T object, Jwt? jwt);
Future<void> afterUpdateSuccess(T before, T after, Jwt? jwt);
Future<void> afterUpdateError(Object error, Jwt? jwt);

beforeUpdate receives the row before the update is applied. afterUpdateSuccess receives two row parameters: the state before and the state after the update.

Update hooks run once per matched row, for PATCH /db, PATCH /db/many, and mutate.update calls from other hooks or cron jobs.

beforeUpdate#

Runs after rules pass, before the UPDATE executes. Can abort by throwing. Nothing is updated, and the client receives a 500 server error rather than your message:

@override
Future<void> beforeUpdate(Event object, Jwt? jwt) async {
  if (object.endDate.isBefore(object.startDate)) {
    throw Exception('End date must be after start date');
  }
}

Note: object here is the current row, not the values being applied. The update hasn't happened yet, and the hook cannot see the incoming values. To validate those, use a row rule.

afterUpdateSuccess#

Runs after the UPDATE commits. Receives both the old and new row states:

@override
Future<void> afterUpdateSuccess(User before, User after, Jwt? jwt) async {
  if (before.email != after.email) {
    // Email changed — require re-verification
    mutate.update.one(
      table: 'users',
      updates: [Update.column('is_verified', UpdateValue.literal(false))],
      where: Eq('id', after.id.value),
    );
    email.send.verifyEmail(
      EmailAddress(address: after.email),
      table: 'users',
    );
  }
}

Use for: detecting field changes, notifying subscribers, resetting verification state.

A mutate.update on the same table fires afterUpdateSuccess again. Guard it with a condition that becomes false after the first pass, as the before.email != after.email check does here. Otherwise the hook keeps re-triggering itself until the chain limit drops the rest.

afterUpdateError#

Runs if the UPDATE fails. Cannot make the operation succeed.

@override
Future<void> afterUpdateError(Object error, Jwt? jwt) async {
  logger.error('Update failed: $error');
}

Detecting Field Changes#

Compare before.<field> and after.<field> in afterUpdateSuccess:

if (before.title != after.title) {
  // Title changed — reindex the post and notify subscribers
}