# Fields

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

A Field within a Collection specifies the attribute's structure and content. Fields are usually auto-detected and linked to the appropriate collection based on your connected data source.

To make an admin panel better for users, it is often important to create new fields that are not directly tied to a database field. These fields may be dynamically computed or sourced from other data sources or services.

For example, instead of storing a user's age, a database typically stores their birthdate. Showing the age instead of the birthdate directly on the user interface improves readability and simplifies data segmentation. Several other examples exist, such as the full name, the number of orders, the total amount spent, a tag indicating the risk level of a transaction, etc.

{% 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 and fields names as needed.

{% 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('users', (collection) => {
    collection.addField('full_name', {
      columnType: 'String',
      dependencies: ['first_name', 'last_name'],
      getValues: (records, context) => {
        return records.map(r => `${r.first_name} ${r.last_name}`);
      }
    });
  });
}
```

{% 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>) {
  // Insert the code snippet here.
}
```

### Creating a Field

First, you need to call the `customizeCollection()` method on the agent.

```typescript
agent.customizeCollection('users', (collection) => {
  // ...
});
```

Arguments:

* `name`<mark style="color:red;">\*</mark> *<mark style="color:green;">String</mark>*: The name of the collection to customize.
* `handle`<mark style="color:red;">\*</mark> *<mark style="color:green;">Function</mark>*: A function that has the collection instance as an argument to start customizing it.

To create a field, use the `addField()` method on your collection instance.

```typescript
agent.customizeCollection('users', (collection) => {
  collection.addField('full_name', {
    columnType: 'String',
    dependencies: [],
    getValues: () => {
      return [];
    }
  });
});
```

Arguments:

* `name`<mark style="color:red;">\*</mark> *<mark style="color:green;">String</mark>*: The name of the field.
* `definition`<mark style="color:red;">\*</mark> *<mark style="color:green;">Object</mark>*: A JavaScript object that contains the definition of the field:
  * `columnType`<mark style="color:red;">\*</mark> *<mark style="color:green;">String</mark>*: The type of the new field which can be [any primitive](/cloud/core-concepts/fields/types.md#primitive-types) or [composite type](/cloud/core-concepts/fields/types.md#composite-types).
  * `dependencies`<mark style="color:red;">\*</mark> *<mark style="color:green;">\[String]</mark>*: An array of field or relationship names that the custom field value depends on.
  * `getValues`<mark style="color:red;">\*</mark> *<mark style="color:green;">Function</mark>*: A function that processes new values in batches, returning an array in the same order as the input records.
    * `records` *<mark style="color:green;">\[Object]</mark>*: An array of JavaScript objects representing the list of records that need to have the field value computed.
    * `context` *<mark style="color:green;">Object</mark>*: The context data.

***

{% hint style="info" %}
Creating fields can impact the performance of your admin panel. It is crucial to review [best practices for fields ](/cloud/best-practices/performance.md#fields)to guarantee an optimal experience for your admin panel users.
{% endhint %}

### Importing a field

Reflecting your database structure as-is in the admin panel can make it harder for users. A good practice is to simplify the interface to help users work faster. One common way is to bring a field from a relationship directly into its parent record.

<details>

<summary>Database schema in this example</summary>

```
Table: users
+----+-----------+------------+------------+
| ID | firstName | lastName   | addressId  |
+----+-----------+------------+------------+
|    |           |            |            |
+----+-----------+------------+------------+

Table: addresses
+----+------------+--------------+-----------+------------+
| ID | streetName | streetNumber | city      | countryId  |
+----+------------+--------------+-----------+------------+
|    |            |              |           |            |
+----+------------+--------------+-----------+------------+

Table: countries
+----+-------+
| ID | name  |
+----+-------+
|    |       |
+----+-------+
```

</details>

```typescript
agent.customizeCollection('users', (collection) => {
  users
    .importField('city', { path: 'addresses:city', readonly: true })
    .importField('country', { path: 'addresses:countries:name', readonly: true });
});
```

The `users` collection now includes two direct fields: `city` and `country`. You can set these fields to be editable or not by toggling the `readonly` option.

### Renaming a field

Renaming a field can improve readability and can be done using the `renameField()` method.

```typescript
agent.customizeCollection('users', (collection) => {
  users.renameField('account_v3_uuid_new', 'account');
});
```

{% hint style="info" %}
Renaming fields only affects their display in the admin panel. To access them in your code, always use their original names.
{% endhint %}

### Removing a field

To hide fields in the UI for technical, confidentiality, or any other reasons, use the `removeField()` method.

```typescript
agent.customizeCollection('users', (collection) => {
  users.removeField('password');
});
```

{% hint style="info" %}
Removing fields only affects their display in the admin panel. However, you can still access them in your code, for example, as dependencies to compute new fields.
{% endhint %}

### Examples

{% hint style="info" %}
To make the code easier to read, all the code snippets below should be wrapped in the following code. Ensure you update the collection and action names as needed.

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

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

{% endhint %}

#### **Concatenating two fields**

```typescript
collection.addField('full_name', {
  columnType: 'String',
  dependencies: ['first_name', 'last_name'],
  getValues: (records, context) => {
    return records.map(r => `${r.first_name} ${r.last_name}`);
  }
});
```

#### **Depending on a "many-to-one" relationship**

In the example below, adding `address:city` to the list of `dependencies` makes the related data available in the `getValues()` function.

<details>

<summary>Database schema in this example</summary>

```
Table: users
+----+-----------+----------------+-----------+
| ID | addressId |   firstName    | lastName  |
+----+-----------+----------------+-----------+
|    |           |                |           |
+----+-----------+----------------+-----------+

Table: address
+----+-------+
| ID | city  |
+----+-------+
|    |       |
+----+-------+
```

</details>

```typescript
collection.addField('displayName', {
  columnType: 'String',
  dependencies: ['firstName', 'lastName', 'address:city'],
  getValues: (records, context) =>
    records.map(r => `${r.firstName} ${r.lastName} (from ${r.address.city})`),
});

```

#### **Depending on a "one-to-many" relationship**

In the example below, we want to add a `users.totalSpending` field by summing the amounts of all `orders`.

<details>

<summary>Database schema in this example</summary>

```
Table: users
+----+
| ID |
+----+
|    |
+----+

Table: orders
+----+-------------+--------+
| ID | customer_id | amount |
+----+-------------+--------+
|    |             |        |
+----+-------------+--------+
```

</details>

1. **Retrieve record IDs**: Start by getting all the record IDs.
2. **Filter orders for current users**: Use these IDs to filter orders that belong to current users.
3. **Aggregate orders by user**: Group the orders by `customer_id` and sum up the total order amount for each user.
4. **Display totals**: Finally, show the total amount spent by each user. If a user hasn't placed any orders, their total will be shown as 0.

```typescript
collection.addField('totalSpending', {
  columnType: 'Number',
  dependencies: ['id'],
  getValues: async (records, context) => {
    const recordIds = records.map(r => r.id);

    const filter = {
      conditionTree: { field: 'customer_id', operator: 'In', value: recordIds },
    };

    const aggregation = {
      operation: 'Sum',
      field: 'amount',
      groups: [{ field: 'customer_id' }],
    };

    const rows = await context.dataSource
      .getCollection('order')
      .aggregate(filter, aggregation);

    return records.map(record => {
      const row = rows.find(r => r.group.customer_id === record.id);
      return row?.value ?? 0;
    });
  },
});
```

#### **Fetching data from an API**

Suppose we want to see if we can actually send emails to our users' email addresses. We can use a checking tool called a verification API to do this job. The API we're using is not real, and this is how it responds:

```json
{
  "username1@domain.com": {
    "usernameChecked": false,
    "usernameValid": null,
    "domainValid": true
  },
  "username2@domain.com": {
    "usernameChecked": false,
    "usernameValid": null,
    "domainValid": true
  }
}
```

<details>

<summary>Database schema in this example</summary>

```
Table: users
+----+-------+
| ID | Email |
+----+-------+
|    |       |
+----+-------+
```

</details>

```typescript
collection.addField('emailDeliverable', {
  columnType: 'Boolean',
  dependencies: ['email'],
  getValues: async (records, context) => {
    const response = await emailVerificationClient.verifyEmails(
      records.map(r => r.email),
    );

    return records.map(r => {
      const check = response[r.email];
      return check.domainValid && (!usernameChecked || usernameValid);
    });
  },
});
```


---

# 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/fields.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.
