Hook Signatures#
Future<void> beforeCreate(T object, Jwt? jwt);
Future<void> afterCreateSuccess(T object, Jwt? jwt);
Future<void> afterCreateError(Object error, Jwt? jwt);
The object in beforeCreate contains the data to be inserted (not yet in the database). The
object in afterCreateSuccess is the committed row, including generated fields like
id and createdAt.
Create hooks run for POST /db and POST /db/many (once per row), and for rows created by
mutate.create from another hook or a cron job. Sign-ups do not fire them.
A new account from the auth endpoints fires beforeSignUp and onSignUp
instead.
beforeCreate#
Runs after rules pass, before the INSERT executes. Can abort the operation by throwing. Nothing is inserted.
To refuse the create and tell the client why, throw an HttpError with a 4xx
status. The client receives that status and the error envelope, {"error": {"code": ..., "message": ..., "details": ...}}:
@override
Future<void> beforeCreate(Task object, Jwt? jwt) async {
if (object.title.isEmpty) {
throw const HttpError.badRequest(
code: 'empty_title',
message: 'Title cannot be empty',
);
}
}
The code, message and details go to the client as written, so keep internals out of them. Any other throw, including an
HttpError whose status is not 4xx, still aborts the create, but the client receives a
500 server error rather than your message.
Use for: extra validation beyond what rules check.
Changing object here has no effect on what is inserted: the hook receives a copy. To fill in a column the client did not send, patch the new row with
mutate.update from afterCreateSuccess.
afterCreateSuccess#
Runs after the INSERT commits. Cannot abort — the row is already in the database.
@override
Future<void> afterCreateSuccess(User user, Jwt? jwt) async {
email.send.verifyEmail(
EmailAddress(address: user.email),
table: 'users',
);
}
Use for: sending welcome or verification emails, creating companion rows, logging new signups, incrementing counters.
afterCreateError#
Runs if the INSERT fails. Cannot make the operation succeed.
@override
Future<void> afterCreateError(Object error, Jwt? jwt) async {
logger.error('Create failed: $error');
}
Use for: logging unexpected insert failures, alerting on anomalies.
Example#
import 'package:my_app/src/schemas/items.dart';
import 'package:zonai_schema/zonai_schema.dart';
class ItemExtensions extends Extension<Item> {
ItemExtensions() : super(items);
@override
Future<void> beforeCreate(Item object, Jwt? jwt) async {
logger.debug('Creating an item');
}
@override
Future<void> afterCreateSuccess(Item object, Jwt? jwt) async {
// Patch the new row. Queued, so it runs after this request's INSERT.
mutate.update.one(
table: 'items',
updates: [Update.column('body', .literal('Updated by extension'))],
where: Eq('id', object.id),
);
}
}
ItemExtensions main() => ItemExtensions();