Skip to main content
In the legacy agent, declaring a smart field was done in one big step. In the new agent, the process is split into multiple steps depending on the capabilities of the field (writing, filtering, sorting, etc.). This allows reuse of the same API when customizing normal fields, reducing the API surface you need to learn.

API cheatsheet

Legacy agentNew agent
get: (record) => { ... }getValues: (records) => { ... }
set: (record, value) => { ... }.replaceFieldWriting(...)
filter: ({ condition, where }) => { ... }.replaceFieldOperator(...) / .emulateFieldOperator(...) / .emulateFieldFiltering(...)
type: 'String'columnType: 'String'
enums: ['foo', 'bar']columnType: 'Enum', enumValues: ['foo', 'bar']
reference: 'otherCollection.id'Use a relationship

Do you still need a computed field?

Smart fields were flexible but often a performance bottleneck. Before migrating, consider whether to replace them with simpler alternatives:
  • If you were moving a field from one collection to another → use import field
  • If you were creating a link to another record → use relationships

Step 1: Implement a read-only field

Dependencies are explicit

You now need to declare a dependencies array: the field names that your getValues function needs. Unlike the legacy agent, the new agent will not automatically fetch the whole record.

Fields work in batches

The get function is now called getValues: it takes an array of records and must return an array of values in the same order.

Other API changes

  • type was renamed to columnType
  • The field property no longer exists. The field name is the first argument of addField
  • reference no longer exists. Use smart relationships
  • enums was renamed to enumValues

Example

collection('users', {
  fields: [
    {
      field: 'full_address',
      type: 'String',
      get: async user => {
        const addr = await geoWebService.getAddress(user.address_id);
        return [addr.line_1, addr.line_2, addr.city, addr.country].join('\n');
      },
    },
  ],
});

Step 2: Implement write handler

If your smart field was writable, use replaceFieldWriting:
collection('users', {
  fields: [{
    field: 'full_address',
    type: 'String',
    get: /* ... */,
    set: async (user, value) => {
      const parts = value.split('\n');
      // update address...
      return {};
    },
  }],
});

Step 3: Implement filters

Filtering is now done operator by operator instead of using a single function. This allows more fine-grained control and means you only need to implement the operators you actually use.
agent.customizeCollection('users', users => {
  users
    .addField('full_address', { /* ... */ })

    // Implement only the operators you need
    .replaceFieldOperator('full_address', 'Equal', (value, context) => ({
      aggregator: 'And',
      conditions: [
        { field: 'address_city', operator: 'Equal', value: value.split('\n')[2] },
      ],
    }))

    // Emulate all other operators (slower, but works automatically)
    .emulateFieldFiltering('full_address');
});
Emulation forces the agent to retrieve all records and compute values for each one. Use it sparingly for collections with many records.