# Collections

{% hint style="success" %}
This is the official documentation of Forest Admin Cloud.
{% endhint %}

Forest Admin automatically provides a default behavior for create, read, update, and delete operations.

In some situations, it may be necessary to inject custom logic either before or after the standard operations.  This can be achieved by using hooks.

Taking it a step further, if you require a complete overhaul of the operation's logic, you can also overwrite the default code.

{% hint style="info" %}
The following sections assume that you have correctly followed all the steps in the [Code Customization Getting Started](/cloud/code-customization/getting-started.md) guide.
{% endhint %}

### TL;DR

Ensure you update the collection name as needed and the logic to verify if the user can perform the update operation.

{% code title="src/index.ts" %}

```typescript
import type { Agent } from '@forestadmin/forest-cloud';
import { Schema } from '../typings';

export default function customizeAgent(agent: Agent<Schema>) {
  agent.customizeCollection('contacts', (collection) => {
    collection.addHook('Before', 'Update', async (context) => {
      const isAllowed = false; // Replace this line by your own logic
      if (!isAllowed) {
        context.throwForbiddenError(`${context.caller.email} is not allowed!`);
      }
    });
  });
}
```

{% endcode %}

***

To make the code easier to read, all the code snippets below should be wrapped in the following code.

```typescript
import type { Agent } from '@forestadmin/forest-cloud';
import { Schema } from '../typings';

export default function customizeAgent(agent: Agent<Schema>) {
  agent.customizeCollection('contacts', (collection) => {
    // Insert the code snippet here.
  });
}
```

### Adding a collection hook

{% hint style="info" %}
Collection hooks are triggered exclusively by UI interactions. They are not executed by backend processes or other interactions outside the Forest Admin UI.
{% endhint %}

To add a hook on a collection, you need to call the `addHook()` method on the collection instance.

```typescript
collection.addHook('Before', 'Update', async (context) => {
  // ...
});
```

Arguments:

* `position`<mark style="color:red;">\*</mark> *<mark style="color:green;">String</mark>*: Indicate when the hook is executed relative to the default logic. Use Before or After.
* `type` <mark style="color:red;">\*</mark> *<mark style="color:green;">String</mark>*: The operation type, which can be Aggregate, Create, Delete, List or Update.
* `handle`<mark style="color:red;">\*</mark> *<mark style="color:green;">Function</mark>*: An async function that contains your hook logic, which is executed before or after (depending on `position`) the default operation.
  * `context` *<mark style="color:green;">Object</mark>*: The `context` object includes a `caller` object. This nested object contains information about the current user, such as the user's timezone and email address, along with other relevant properties.

Note that a single collection can support multiple hooks of the same type and position. These hooks execute sequentially, based on their order of declaration.&#x20;

### Overriding a collection

{% hint style="warning" %}
This feature should be implemented with caution and full understanding. It's important to note that the default behavior will not execute. Therefore, it is your responsibility to anticipate all the potential use cases to ensure the operation's correct usage.
{% endhint %}

To override the default behavior of an operation, you can call `overrideCreate()`, `overrideUpdate()`, or `overrideDelete()`, as needed.

#### Create

```typescript
collection.overrideCreate(async (context) => {
  // Your business logic here.
  return [];
});
```

Arguments:

* `context` *<mark style="color:green;">Object</mark>*: The `context` object includes a `data` object. This nested object contains the data intended for creation.

This method must returns an array of created records.

#### Update

```typescript
collection.overrideUpdate(async (context) => {
  // Your business logic here.
});
```

Arguments:

* `context` *<mark style="color:green;">Object</mark>*: The `context` object includes a `filter` and a `data` objects. These nested objects contains respectively the records targeted and the data for the update.

#### Delete

```typescript
collection.overrideDelete(async (context) => {
  // Your business logic here.
});
```

Arguments:

* `context` *<mark style="color:green;">Object</mark>*: The `context` object includes a `filter` object. This nested object contains the records targeted.

### Examples

#### Check permissions before a record update using a Before Update hook

```typescript
collection.addHook('Before', 'Update', async (context) => {
  const isAllowed = await myFunctionToCheckIfUserIsAllowed(context.caller.email);
  if (!isAllowed) {
    context.throwForbiddenError(`${context.caller.email} is not allowed!`);
  }
});
```

#### Send an email after a record creation using a After Create hook

```typescript
collection.addHook('After', 'Create', async (context, responseBuilder) => {
  const userEmail = context.records[0]?.email;
  await myEmailService.sendEmail({
    from: 'erlich@bachman.com',
    to: userEmail,
    message: 'Hi there, your account has been successfully created.',
  });
});
```

#### Create a record via an external API call

```typescript
collection.overrideCreate(async context => {
  const response = await fetch('https://my-contact-api.com/contacts', {
    method: 'POST',
    body: context.data,
  });
  
  const contacts = await response.json();
  return contacts;
});
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.forestadmin.com/cloud/code-customization/collections.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
