> For the complete documentation index, see [llms.txt](https://docs.mediarithmics.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.mediarithmics.io/setup-and-monitor/extend-the-platform/build-a-computed-field.md).

# Build a computed field

Write the plugin behind a computed field: the methods, the state, and the tests.

## Your goal

Implement the Computed Field Function that produces a computed field's value - correctly enough that it survives an initial loading where event order is not guaranteed.

## Expected outcome

A `COMPUTED_FIELD_FUNCTION` plugin whose state stays small, whose result is stable, and which behaves identically however the events arrive.

## Before you start

* Read [Computed fields](/setup-and-monitor/collect-and-manage-your-data/design-your-data-model-and-schema/computed-fields.md) for what a computed field is, its lifecycle, and how it is declared in the schema.
* Read [Plugin architecture](/setup-and-monitor/extend-the-platform/plugin-architecture.md) for the plugin, version, and instance model.

{% hint style="info" %}
Computed fields are created as plugins, which requires collaboration with the mediarithmics Professional Services team: defining the logic, writing the plugin specification, developing the plugin, and configuring the computed field. Discuss your needs with your account manager.
{% endhint %}

Create the plugin with the type `COMPUTED_FIELD_FUNCTION`, following the standard creation and deployment procedure described in [Build a custom feed plugin](/setup-and-monitor/extend-the-platform/build-a-custom-feed-plugin.md#create-and-deploy), then generate a version from your build.

## The four methods

You are required to implement **four** functions from the Plugin SDK: three `onUpdate` methods that maintain the **state**, and `buildResult`, which turns a state into a **result**.

{% tabs %}
{% tab title="onUpdateActivity" %}

```
onUpdateActivity(state: State, userActivity: UserActivity): State
```

Updates the state with a new user activity, and **removes outdated activities**.

|        |                                                                             |
| ------ | --------------------------------------------------------------------------- |
| Inputs | `state` - the current state object. `userActivity` - the new activity data. |
| Output | The updated state object.                                                   |

```jsx
onUpdateActivity(state, userActivity) {
    // Logic to update state
    return updatedState;
}
```

{% hint style="info" %}
The activities and profiles the plugin receives are **raw data**. You cannot rely on the structure defined in your schema to understand their shape - inspect a real activity in the user lookup page instead.
{% endhint %}
{% endtab %}

{% tab title="onUpdateUserProfile" %}

```
onUpdateUserProfile(state: State, userProfile: UserProfile, operation: core.Operation): State
```

Handles updates to a user profile.

|        |                                                                            |
| ------ | -------------------------------------------------------------------------- |
| Inputs | `state`, `userProfile`, and `operation` - an enum of `UPDATE` or `DELETE`. |
| Output | The updated state object.                                                  |

```jsx
onUpdateUserProfile(state, userProfile, operation) {
    // Logic to handle UserProfile updates
    return updatedState;
}
```

{% endtab %}

{% tab title="onUpdateComputedFields" %}

```
onUpdateComputedFields(state: State | null): Result
```

Triggered by the result computation of another computed field.

{% hint style="danger" %}
**This method must be implemented even though it is not currently used.** Return the same state, or you will get errors.
{% endhint %}

```jsx
onUpdateComputedFields(state) {
    return state;
}
```

{% endtab %}

{% tab title="buildResult" %}

```
buildResult(state: State | null): Result
```

Computes the result from the current state. The result is **stored for a defined duration**, and querying the field returns the stored value during that period.

```jsx
buildResult(state) {
    // Logic to compute result
    return result;
}
```

{% endtab %}
{% endtabs %}

## The two hard constraints

{% hint style="danger" %}
**The `onUpdate` methods must be commutative.** During the initial loading, event order is **not guaranteed** - the methods must always produce the same result regardless of the sequence in which events are processed.

A running total is commutative. "Keep the last value seen" is not.
{% endhint %}

{% hint style="danger" %}
**The `State` size is restricted to a maximum of 1 MB.** If the limit is exceeded, **the update is discarded and not saved** - silently, from the plugin's point of view. Design the state to hold only what `buildResult` needs.
{% endhint %}

## Worked example

The use case: **calculate the sales over 12 months for items in the IT category, per user**.

{% stepper %}
{% step %}

### Declare the state

The state stores the order amounts per day, for IT items, over the last 12 months:

```typescript
export interface State {
  activities_for_the_last_12_months: {
    [date: number]: [{
      amount: number;
    }]
  }
}
```

Keying by day is what makes the lookback window enforceable: dropping days older than 12 months is a cheap operation on this shape.
{% endstep %}

{% step %}

### Declare the result

```typescript
export interface Result {
  IT_amount_for_the_last_12_months: number;
}
```

The result sums the values stored in the state.
{% endstep %}

{% step %}

### Declare the activity shape you read

```typescript
interface Items {
    category: string;
    price: number;
}

interface UserActivity {
  items_bought: Items[];
}
```

{% hint style="info" %}
To find your real activity structure, look at an activity returned in the user lookup page in the Navigator. See [Explore a user profile](/setup-and-monitor/operate-and-monitor/explore-a-user-profile.md).
{% endhint %}
{% endstep %}

{% step %}

### Implement the class

```typescript
export class MyComputedField extends core.ComputedFieldPlugin<
    State, Result, UserActivity, UserProfile, ComputedField
> {
    constructor() {
        super();
    }
    // onUpdateActivity, onUpdateUserProfile, onUpdateComputedFields, buildResult
}
```

{% endstep %}
{% endstepper %}

The same approach scales to several categories rather than one - which leads directly to the design question below.

## Design guidance

**One plugin or several?**

| Situation                                                           | Decision         |
| ------------------------------------------------------------------- | ---------------- |
| Two scores need **the same** data in the state                      | **One plugin.**  |
| Two scores need **different** data - the state would be polymorphic | **Two plugins.** |

**Maximise the number of scores calculated by one Computed Field Function.** The same state then serves several scores, and you avoid storing the same state several times.

**Other rules that come from experience:**

* **On error, always return the same state, or `null`.** Returning anything else deletes the state, and therefore resets the result.
* **Design the state to store relevant information efficiently.** It is capped at 1 MB.
* **Clean the state against the lookback window**, for both profiles and activities: when updating, remove data that is no longer useful to compute the result.
* **When editing a live computed field, you must relaunch an initial loading through the API.**

## Test before deploying

Create tests covering at least:

* Adding new activities and updating profiles, and verifying the state is updated correctly - **including that the order in which you send profile updates and activities has no impact**.
* That activities no longer relevant to the result are removed from the state.
* That the correct values are returned in the result.

## Verify

* Tests pass with events sent in several different orders.
* The state stays well below 1 MB on your heaviest test user.
* After the initial loading, `SELECT { your_field } FROM UserPoint` returns plausible values.
* An error path leaves the previous result intact rather than resetting it.

## Related pages

<table data-view="cards" data-search="false"><thead><tr><th></th><th></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td><strong>Computed fields</strong></td><td>The concept, lifecycle, and schema declaration.</td><td><a href="/setup-and-monitor/collect-and-manage-your-data/design-your-data-model-and-schema/computed-fields.md">Computed fields</a></td></tr><tr><td><strong>Plugin architecture</strong></td><td>Versions, instances, and properties.</td><td><a href="/setup-and-monitor/extend-the-platform/plugin-architecture.md">Plugin architecture</a></td></tr><tr><td><strong>Build a custom feed plugin</strong></td><td>The shared creation and deployment procedure.</td><td><a href="/setup-and-monitor/extend-the-platform/build-a-custom-feed-plugin.md">Build a custom feed plugin</a></td></tr></tbody></table>


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

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

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.mediarithmics.io/setup-and-monitor/extend-the-platform/build-a-computed-field.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

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.
