Skip to content
BunBase BunBase BunBase Docs Alpha v0.1.0

Lifecycle Hooks

Lifecycle hooks let you run server-side JavaScript before or after record operations in a collection — without modifying the server code.

Hooks are stored in the database and executed synchronously on every matching operation.

EventWhen it runsAbort supportRecord mutations
beforeCreateBefore a record is insertedYes — throw to abortYes — mutate record to modify data
afterCreateAfter a record is insertedNo — errors are logged onlyIgnored
beforeUpdateBefore a record is updatedYes — throw to abortYes — mutate record (the patch)
beforeDeleteBefore a record is soft-deletedYes — throw to abortIgnored

Hooks run in a best-effort sandbox: the following globals are shadowed to undefined inside hook code: Bun, process, require, fetch, eval, Function, XMLHttpRequest, WebSocket, Worker, Blob, File. Hooks cannot access these APIs.

Hooks that exceed 500 ms of synchronous execution are killed with an error.

This is a JavaScript-level sandbox — admin-created hooks should still be treated as trusted code. A subprocess-based OS-level sandbox is planned for a future release.

Hook code is a function body executed with two named arguments:

  • record — the record data object. For beforeCreate and beforeUpdate, mutations to this object are applied to the write. For beforeDelete, this is a snapshot of the record being deleted.
  • context — metadata about the operation:
    {
    collection: string, // Collection name
    event: string, // "beforeCreate" | "afterCreate" | "beforeUpdate" | "beforeDelete"
    userId: string | null // ID of the authenticated user making the request, or null
    }

Validate a required field:

if (!record.title || !record.title.trim()) {
throw new Error("title is required");
}

Set a default value:

if (!record.status) {
record.status = "draft";
}

Reject deletes for published records:

if (record.status === "published") {
throw new Error("Cannot delete published records.");
}

Log after creation (non-fatal):

// afterCreate — errors here are logged, not surfaced to the client
console.log("New record created:", record._id);

For beforeCreate, beforeUpdate, and beforeDelete:

  • If your hook throws an error, the operation is aborted.
  • A 400 Bad Request response is returned with the error message from the thrown error.
  • No database write occurs.

For afterCreate:

  • Errors are caught and logged to the server app log.
  • The request succeeds regardless of hook errors.
  • Hooks are synchronousasync/await, Promise, fetch, and other async patterns are not supported.
  • Hooks run in the same process as the DB handler — heavy computation will block writes.
  • No access to external modules or Node/Bun APIs — only pure JS logic.
  • No persistent state between hook invocations.

All hook management requires the ADMIN_SECRET.

GET /api/v1/admin/hooks
Authorization: Bearer <admin-secret>

Filter by collection:

GET /api/v1/admin/hooks?collection=posts

Response:

{
"items": [
{
"id": "01JKX...",
"collection": "posts",
"event": "beforeCreate",
"code": "if (!record.title) throw new Error(\"title required\");",
"enabled": true,
"created_at": 1709900000000
}
]
}
POST /api/v1/admin/hooks
Authorization: Bearer <admin-secret>
Content-Type: application/json
{
"collection": "posts",
"event": "beforeCreate",
"code": "if (!record.title) throw new Error(\"title is required\");",
"enabled": true
}

Fields:

  • collection (required) — collection name
  • event (required) — "beforeCreate", "afterCreate", "beforeUpdate", or "beforeDelete"
  • code (required) — JS function body string
  • enabled (optional, default true) — whether the hook runs

Response 201: the created hook object.

GET /api/v1/admin/hooks/:id
Authorization: Bearer <admin-secret>
PATCH /api/v1/admin/hooks/:id
Authorization: Bearer <admin-secret>
Content-Type: application/json
{
"code": "if (!record.title) throw new Error(\"title is required\");",
"enabled": false
}

Both fields are optional. Only provided fields are updated.

DELETE /api/v1/admin/hooks/:id
Authorization: Bearer <admin-secret>

Open a collection in Studio and click the Hooks tab. From there you can:

  • See all hooks with their event type, enabled status, and code preview
  • Add a new hook with the event selector and code editor
  • Toggle a hook on or off without deleting it
  • Edit hook code
  • Delete a hook
  • Hook code runs inside the BunBase server process with full access to the JavaScript runtime.
  • Only trusted administrators (those with ADMIN_SECRET) can create or modify hooks.
  • Never expose the Admin API to untrusted users.
  • Hook code cannot directly access the SQLite database or BunBase internals — it only receives and mutates the record object.
  • All hooks for a collection are loaded from the database and cached in memory. Changes via the Admin API invalidate this cache immediately.
  • Hooks are deleted automatically when their collection is dropped.