Collection hook
This is the official documentation of the @forestadmin/agent Node.js agent.
Forest Admin allows customizing at a very low level the behavior of any given Collection via the usage of Collection Hooks.
How it works
Any given Collection should implement all of the following functions:
listcreateupdatedeleteaggregate
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 InterfacedataSource- the composite data source containing all collectionscaller- information about the user performing the operation
The caller object
caller objectThe caller object contains information about the current user:
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:
throwValidationError(message)
Display a validation error
throwForbiddenError(message)
Display a forbidden error
throwError(message)
Display a generic error
Hook-specific context properties
Each hook type provides additional 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
A single Collection can have multiple Hooks with the same position and the same type. They will run in their declaration order. Collection Hooks are only called when the Collection function is contacted by the UI. This means that any usage of the Forest Admin query interface will not trigger them.
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.
transaction.addHook('Before', 'Update', async context => {
// context.caller contains information about the current user, the defined
// timezone, etc.
// In this case, context.caller.email is the email used in Forest Admin by the user
// that initiated the call
const isAllowed = await myFunctionToCheckIfUserIsAllowed(context.caller.email);
if (!isAllowed) {
// Raising an error here will prevent the execution of the update function,
// as well as any other hooks that may be defined afterwards.
context.throwForbiddenError(`${context.caller.email} is not allowed!`);
}
});Another good example would be the following: Each time a new User is created in the database, I want to send him an email.
user.addHook('After', 'Create', async (context, responseBuilder) => {
// The result of the create function always return an array of records
const userEmail = context.records[0]?.email;
await MyEmailSender.sendEmail({
from: '[email protected]',
to: userEmail,
message: 'Hey, a new account was created with this email.',
});
});Advanced examples
Preventing deletion of protected records
agent.customizeCollection('users', collection => {
collection.addHook('Before', 'Delete', async context => {
const records = await context.collection.list(context.filter, [
'protected',
'email',
]);
const protectedRecords = records.filter(r => r.protected);
if (protectedRecords.length > 0) {
context.throwForbiddenError(
`Cannot delete protected users: ${protectedRecords
.map(r => r.email)
.join(', ')}`,
);
}
});
});Querying other collections via dataSource
agent.customizeCollection('orders', collection => {
collection.addHook('Before', 'Create', async context => {
// Access another collection via dataSource
const customersCollection = context.dataSource.getCollection('customers');
for (const order of context.data) {
const [customer] = await customersCollection.list(
{
conditionTree: { field: 'id', operator: 'Equal', value: order.customerId },
},
['creditLimit', 'currentBalance'],
);
if (customer.currentBalance + order.amount > customer.creditLimit) {
context.throwValidationError(
`Order exceeds credit limit for customer ${order.customerId}`,
);
}
}
});
});Last updated
Was this helpful?