Skip to content

Lifecycle hooks ​

Hooks run on the server inside the request, after validation (and after create-scope is applied), around the adapter write.

ts
admin.register("User", {
  beforeCreate: async (data) => {
    if (typeof data.password === "string") {
      data.passwordHash = await hash(data.password);
      delete data.password;
    }
    return data;
  },
  afterCreate: async (record) => {
    await notifyOps(`Created user ${record.id}`);
  },
  beforeUpdate: async (id, data) => data,
  afterUpdate: async (record) => {},
  beforeDelete: async (id) => {
    if (id === SUPER_ADMIN_ID)
      throw new Error("Cannot delete the root operator.");
  },
  afterDelete: async (id) => {},
});
HookWhenCan change data
beforeCreateAfter validate + create-scope, before createyes, return the object
afterCreateAfter insertno
beforeUpdateAfter the scoped row is found + validate + scope-field lock, before updateManyyes
afterUpdateAfter reloadno
beforeDeleteAfter the scoped row is found, before deleteManythrow to abort
afterDeleteAfter deleteno

Useful jobs ​

  • Hash a password (and keep password excluded from the schema)
  • Add derived values to fields that are already writable
  • Refuse to delete Linus
  • Fan out to email / queue after a successful write

Thrown errors become 500 INTERNAL_ERROR unless you throw an AdminApiError. Do not put secrets in the message.

Order on create ​

  1. Auth, permission, validate payload
  2. Apply simple scope equalities
  3. beforeCreate
  4. Adapter create
  5. afterCreate
  6. audit.write if configured

Hook output is validated again. Hooks cannot add hidden, read-only, unauthorized, or nested-write fields, and simple scope equalities are re-applied after the hook.

Released under the MIT License.