Skip to Content

Hooks

Hooks are arrays of functions per stage. They run in order. Writes and their hooks run in one transaction: if a hook throws, nothing is written.

import { defineCollection, fields as f } from "@quickben/core"; export const posts = defineCollection({ slug: "posts", fields: { title: f.text({ required: true }), slug: f.text({ unique: true }), status: f.select({ options: ["draft", "published"], required: true, default: "draft" }), publishedAt: f.datetime(), }, hooks: { beforeValidate: [ ({ data }) => data.title && !data.slug ? { ...data, slug: data.title.toLowerCase().replaceAll(/\W+/g, "-") } : data, ], beforeChange: [ ({ data, original }) => { const publishing = data.status === "published" && original?.status !== "published"; return publishing ? { ...data, publishedAt: new Date().toISOString() } : data; }, ], afterChange: [ ({ operation, doc }) => { console.log(`${operation}: post ${doc.id}`); }, ], }, });

Stages

HookExtra argumentsReturn
beforeValidateoperation, data (not validated), originalnew data or nothing
beforeChangeoperation, data (validated), originalnew data or nothing
afterChangeoperation, doc, originalnothing
afterReaddocnew document or nothing
beforeDeleteid, docnothing. Throw to cancel.
afterDeleteid, docnothing

Every hook also gets user, req, qb and collection. qb is bound to the current transaction. original is null on create.

Order of a write

OperationSteps
createaccess → defaults → beforeValidate → validation → related ids → beforeChange → access filter on new doc → write → afterChange → afterRead
updateload → access (filter must match stored doc) → beforeValidate → validation → related ids → beforeChange → write → afterChange → afterRead
deleteload → access (filter must match) → beforeDelete → delete → afterDelete → afterRead
readaccess filter AND query → adapter → relations → afterRead → select

When a collection has afterRead hooks, core loads full documents and applies select after the hooks.

Last updated on