Collection hook

Forest Admin allows customizing at a very low level the behavior of any given Collection via the usage of Collection Hooks.

Collection Hooks are a very powerful feature and require special care when using them.

How it works

Any given Collection should implement all of the following functions:

  • list

  • create

  • update

  • delete

  • aggregate

The Collection Hooks feature allows executing code before and/or after any of these functions, providing an easy way to interact with your Collections.

To declare a Hook on a Collection, the following information is required:

  • A lifecycle position (Before | After)

  • An action type (List | Create | Update | Delete | Aggregate)

  • A callback, that will receive a context matching the provided hook position and hook definition.

Context object reference

All hook contexts provide access to:

  • collection - the current collection, which can be queried using the Forest Admin Query Interface

  • dataSource - the composite data source containing all collections

  • caller - information about the user performing the operation

The caller object

The caller object contains information about the current user:

Property
Description

id

User ID

email

User email

firstName

First name

lastName

Last name

team

Team name

role

Role name

tags

Custom tags

timezone

User timezone

Error methods

All contexts provide methods to throw errors that will be displayed in the Forest Admin UI:

Method
Description

throw_validation_error(message)

Display a validation error

throw_forbidden_error(message)

Display a forbidden error

throw_error(message)

Display a generic error

Hook-specific context properties

Each hook type provides additional properties:

Hook
Position
Properties

List

Before

filter (paginated filter), projection (fields to return)

List

After

Same as Before + records (returned records)

Create

Before

data (data to create)

Create

After

data + records (created records)

Update

Before

filter, patch (read-only)

Update

After

filter, patch (read-only)

Delete

Before/After

filter (filter for records to delete)

Aggregate

Before

filter, aggregation, limit

Aggregate

After

Same as Before + aggregateResult

Basic use cases

In the following example, we want to prevent a set of users from updating any records of the Transactions table. We want to check if the user email is allowed to update a record via an external API call.

from forestadmin.datasource_toolkit.decorators.hook.context.update import (
    HookBeforeUpdateContext
)

async def transaction_before_update_fn(context: HookBeforeUpdateContext):
    is_allowed = my_function_to_check_user_is_allowed(context.caller.email)
    if not is_allowed:
        context.throw_forbidden_error(f"{context.caller.email} is not allowed")

agent.customize_collection("Transactions").add_hook(
    "Before", "Update", transaction_before_update_fn
)

Another good example would be the following: Each time a new User is created in the database, I want to send him an email.

agent.customize_collection("User").add_hook(
    "After",
    "Create",
    lambda context: MyEmailSender.send_email(
        {
            "from": "[email protected]",
            "to": context.records[0]['email'],
            "message": "Hey, a new account was created with this email.",
        }
    ),
)

Advanced examples

Preventing deletion of protected records

async def before_delete_user(context):
    records = await context.collection.list(context.filter, ['protected', 'email'])

    protected_records = [r for r in records if r.get('protected')]
    if protected_records:
        emails = ', '.join(r['email'] for r in protected_records)
        context.throw_forbidden_error(f"Cannot delete protected users: {emails}")

agent.customize_collection("user").add_hook("Before", "Delete", before_delete_user)

Querying other collections via dataSource

async def before_create_order(context):
    customers_collection = context.datasource.get_collection('customer')

    for order in context.data:
        filter = ConditionTreeLeaf('id', 'equal', order['customer_id'])
        customers = await customers_collection.list(filter, ['credit_limit', 'current_balance'])
        customer = customers[0]

        if customer['current_balance'] + order['amount'] > customer['credit_limit']:
            context.throw_validation_error(f"Order exceeds credit limit for customer {order['customer_id']}")

agent.customize_collection("order").add_hook("Before", "Create", before_create_order)

Last updated

Was this helpful?