---
product: "AG Grid"
title: "Calculated Columns - API, Events & State"
description: "Manage JavaScript Data Grid calculated columns in code: read and update their column definitions, save and restore user-created columns with Grid State, and listen for calculated column events."
enterprise: true
framework: javascript
version: "36.3.0"
related:
    - title: "Dialog & Customisation"
      url: "https://www.ag-grid.com/archive/36.3.0/javascript-data-grid/calculated-columns-dialog/"
    - title: "Restrictions & Validation"
      url: "https://www.ag-grid.com/archive/36.3.0/javascript-data-grid/calculated-columns-validation/"
llms: "https://www.ag-grid.com/archive/36.3.0/llms.txt"
---

# Calculated Columns - API, Events & State

Manage [calculated columns](https://www.ag-grid.com/archive/36.3.0/javascript-data-grid/calculated-columns/) in code, save and restore them with [Grid State](https://www.ag-grid.com/archive/36.3.0/javascript-data-grid/grid-state/), and respond to their events.

## Managing Columns with Column Definitions

Manage calculated columns through column definitions, like other columns. Read the current definitions with `api.getColumnDefs()` and write them back with `api.setGridOption('columnDefs', ...)` to add, edit or remove a calculated column. To find the calculated columns in the grid, filter `api.getColumns()` by `calculatedExpression`.

When [processColDef](https://www.ag-grid.com/archive/36.3.0/javascript-data-grid/calculated-columns-dialog/#processcoldef) is set, the definitions from `api.getColumnDefs()` include the callback's output. Loading them back as `columnDefs` declares those columns with that output fixed: the callback no longer runs for them, and the dialog shows the callback's `headerName` as the title.

The following example adds, edits and removes a Profit Margin column through these APIs, and logs calculated-column changes:

#### Calculated Columns API

```ts
import {
  CalculatedColumnChangedEvent,
  ClientSideRowModelModule,
  ColDef,
  ColumnApiModule,
  GridApi,
  GridOptions,
  ModuleRegistry,
  NumberFilterModule,
  ValueFormatterLiteParams,
  createGrid,
  enableDevValidations,
} from "ag-grid-community";
import { CalculatedColumnsModule } from "ag-grid-enterprise";
import { SalesRow } from "./interfaces";

if (process.env.NODE_ENV !== "production") {
  // Enable extended validations only for development
  enableDevValidations();
}

ModuleRegistry.registerModules([
  ClientSideRowModelModule,
  CalculatedColumnsModule,
  ColumnApiModule,
  NumberFilterModule,
]);

type SalesRow = {
  product: string;
  revenue: number;
  cost: number;
};

const currencyFormatter = (
  params: ValueFormatterLiteParams<SalesRow, number>,
) => (params.value == null ? "" : `$${params.value.toLocaleString()}`);

const percentFormatter = (
  params: ValueFormatterLiteParams<SalesRow, number>,
) => (params.value == null ? "" : `${(params.value * 100).toFixed(1)}%`);

const marginColumn: ColDef<SalesRow> = {
  colId: "profitMargin",
  headerName: "Profit Margin",
  calculatedExpression: "([revenue] - [cost]) / [revenue]",
  cellDataType: "percentage",
};

const columnDefs: ColDef<SalesRow>[] = [
  { field: "product", flex: 1 },
  { field: "revenue", cellDataType: "currency" },
  { field: "cost", cellDataType: "currency" },
  {
    colId: "profit",
    headerName: "Profit",
    calculatedExpression: "[revenue] - [cost]",
    cellDataType: "currency",
  },
];

const rowData: SalesRow[] = [
  { product: "Solar panel kit", revenue: 142000, cost: 96000 },
  { product: "Smart thermostat", revenue: 78000, cost: 52000 },
  { product: "Battery pack", revenue: 126000, cost: 101000 },
  { product: "EV charger", revenue: 92000, cost: 61000 },
  { product: "Heat pump", revenue: 168000, cost: 119000 },
  { product: "Inverter unit", revenue: 88000, cost: 57000 },
  { product: "Wind turbine kit", revenue: 232000, cost: 171000 },
  { product: "Solar tile roof", revenue: 198000, cost: 144000 },
  { product: "Power optimiser", revenue: 64000, cost: 41000 },
  { product: "Charge controller", revenue: 53000, cost: 33000 },
  { product: "Energy monitor", revenue: 47000, cost: 29000 },
  { product: "Storage cabinet", revenue: 71000, cost: 52000 },
  { product: "Microinverter", revenue: 59000, cost: 37000 },
  { product: "Heat recovery unit", revenue: 124000, cost: 88000 },
  { product: "Hybrid boiler", revenue: 156000, cost: 117000 },
  { product: "Smart meter", revenue: 39000, cost: 24000 },
  { product: "Insulation pack", revenue: 44000, cost: 27000 },
  { product: "EV cable set", revenue: 31000, cost: 18000 },
  { product: "Solar pump", revenue: 67000, cost: 45000 },
  { product: "Backup generator", revenue: 173000, cost: 131000 },
];

let gridApi: GridApi<SalesRow>;

const gridOptions: GridOptions<SalesRow> = {
  columnDefs,
  rowData,
  dataTypeDefinitions: {
    currency: {
      baseDataType: "number",
      extendsDataType: "number",
      valueFormatter: currencyFormatter,
    },
    percentage: {
      baseDataType: "number",
      extendsDataType: "number",
      valueFormatter: percentFormatter,
    },
  },
  calculatedColumns: true,
  onCalculatedColumnChanged,
  defaultColDef: {
    flex: 1,
    minWidth: 130,
  },
};

function onCalculatedColumnChanged(
  event: CalculatedColumnChangedEvent<SalesRow>,
) {
  console.log(
    "Calculated column changed:",
    event.column.getColId(),
    event.changes,
  );
}

// Get: read the calculated columns currently in the grid.
function logCalculatedColumns() {
  const calculatedColumns = (gridApi.getColumns() ?? []).filter(
    (column) => column.getColDef().calculatedExpression !== undefined,
  );
  console.log(
    "### Calculated columns ###",
    calculatedColumns.map((column) => column.getColId()),
  );
}

// Set: add a calculated column by updating the columnDefs grid option.
function addMarginColumn() {
  const colDefs = gridApi.getColumnDefs() ?? [];
  if (
    colDefs.some(
      (colDef) => "colId" in colDef && colDef.colId === "profitMargin",
    )
  ) {
    return;
  }
  gridApi.setGridOption("columnDefs", [...colDefs, marginColumn]);
}

// Edit: change an existing calculated column's expression.
function editMarginExpression() {
  const colDefs = gridApi.getColumnDefs() ?? [];
  gridApi.setGridOption(
    "columnDefs",
    colDefs.map((colDef) =>
      "colId" in colDef && colDef.colId === "profitMargin"
        ? { ...colDef, calculatedExpression: "([revenue] - [cost]) / [cost]" }
        : colDef,
    ),
  );
}

// Remove: drop a calculated column from the columnDefs grid option.
function removeMarginColumn() {
  const colDefs = gridApi.getColumnDefs() ?? [];
  gridApi.setGridOption(
    "columnDefs",
    colDefs.filter(
      (colDef) => !("colId" in colDef && colDef.colId === "profitMargin"),
    ),
  );
}

const gridDiv = document.querySelector<HTMLElement>("#myGrid")!;
gridApi = createGrid(gridDiv, gridOptions);

if (typeof window !== "undefined") {
  // Attach external event handlers to window so they can be called from index.html
  (<any>window).logCalculatedColumns = logCalculatedColumns;
  (<any>window).addMarginColumn = addMarginColumn;
  (<any>window).editMarginExpression = editMarginExpression;
  (<any>window).removeMarginColumn = removeMarginColumn;
}
```

[Live example: Calculated Columns API](https://www.ag-grid.com/archive/36.3.0/examples/calculated-columns-api/calculated-columns-api/typescript/)

In the example, use the buttons to add, edit and remove Profit Margin, and click **Log Calculated Columns** to list the calculated columns in the grid.

## Saving and Restoring

The dialog manages calculated columns inside the grid without mutating the `columnDefs` array supplied by the application. To persist user-created calculated columns, read the complete current definitions from `api.getColumnDefs()`, or use [Grid State](https://www.ag-grid.com/archive/36.3.0/javascript-data-grid/grid-state/), which captures them and recreates them on restore.

|  | Grid State | `api.getColumnDefs()` |
| --- | --- | --- |
| User-created calculated columns | Captures them and recreates them on restore | Contains them in the complete current definitions |
| With `processColDef` set | Keeps user-created columns with their own values, and applies the callback again on restore | Contains the callback's output. Loaded back as `columnDefs`, the columns keep that output and the callback no longer runs for them |
| Dialog validation errors | Preserves them | Contains the column configuration, not validation errors. Restoring definitions alone treats the expressions as programmatic definitions. |

With `processColDef` set, use Grid State instead to keep user-created columns with their own values. Use Grid State to preserve dialog validation errors as well.

Grid State also captures user edits to, and removals of, calculated columns declared in `columnDefs`. On restore, it re-applies them over the declared definitions, so a stored state takes precedence over a changed declaration.

## Events

Use `calculatedColumnChanged` to track changes to `headerName`, `cellDataType`, `calculatedExpression` and `columnGroupShow`:

- The `changes` object contains only changed properties, with their previous and new values. Cell values read in the handler already reflect the change, including in calculated columns that depend on it.
- Deferred Apply raises one event for all changes. Live mode raises one per applied update, with [applyDebounce](https://www.ag-grid.com/archive/36.3.0/javascript-data-grid/calculated-columns-dialog/#applydebounce) combining edits after a pause in typing. Initialisation and Grid State restoration do not raise this event.
- Check `expressionValidation.valid` before saving an accepted expression. When invalid, `expressionValidation.reason` is `restrictedReference`, `customValidation`, `unknownReference` or `invalidExpression`. Updates that change only the validation result also raise this event, with an empty `changes` object and the unchanged `expression`.

`calculatedColumnValidationStateChanged` reports changes to built-in validation status after an edit is applied, not while it is pending. Callback rejections are reported through `calculatedColumnChanged.expressionValidation` instead.

```js
const gridOptions = {
    onCalculatedColumnChanged: event => {
        const { headerName, cellDataType, calculatedExpression } = event.changes;
        if (headerName) {
            console.log('Title:', headerName.oldValue, headerName.newValue);
        }
        if (cellDataType) {
            console.log('Type:', cellDataType.oldValue, cellDataType.newValue);
        }
        if (calculatedExpression && event.expressionValidation.valid) {
            console.log('Expression:', calculatedExpression.oldValue, calculatedExpression.newValue);
        }
    },
};
```

> **Note**
>
> Live expressions can be incomplete or invalid. Recognised references use stored `colId` values, such as `[revenue]`; references the dialog cannot resolve remain as typed, so they can still contain header names.
>
> `expressionValidation` is a snapshot of syntax, direct reference resolution and saved dialog errors, not row-specific evaluation results. To persist rejected expressions with their blocked status, save [Grid State](https://www.ag-grid.com/archive/36.3.0/javascript-data-grid/grid-state/); saving only the expression text does not retain that status.

## API Reference

### Grid Options

| Property | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `calculatedColumns` | `CalculatedColumnsGridOption` |  |  | Enables Calculated Columns. Set to `true` for defaults or provide options to configure the dialog. Disabled when omitted or `false`. [Module](https://www.ag-grid.com/archive/36.3.0/javascript-data-grid/modules/): `CalculatedColumnsModule`. |

Properties available on the `CalculatedColumnsOptions&lt;TData = any, TContext = any&gt;` interface.

| Property | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `dataTypes` | `string[]` |  | `['text', 'number', 'date', 'boolean']` | Cell data types shown in the Calculated Column dialog type selector. Values must be built-in cell data types or custom types defined in `dataTypeDefinitions`. |
| `expressionPickers` | `CalculatedColumnExpressionPicker[] \| null` |  | `['columns', 'functions', 'operators']` | Expression pickers shown in the Calculated Column dialog expression editor. |
| `isColumnReferenceable` | `Function` |  |  | Return `false` to exclude a source column from the dialog's picker and manually entered references. Called when the dialog opens. Does not restrict programmatic `calculatedExpression` values. Existing expressions can be kept unchanged; expression edits are checked against the restriction. Live edits with restricted references are saved with an error and do not evaluate until corrected. `api.refreshCalculatedColumns()` explicitly rechecks existing expressions against this rule. |
| `getValidationErrors` | `Function` |  |  | Synchronous validation for the dialog's title, type and expression, including drafts with built-in errors. Receives `internalErrors`; the returned messages replace built-in validation feedback. Column restrictions from `isColumnReferenceable` remain enforced. Return messages keyed by field to reject its value; `null`, omitted fields and empty arrays accept it. Deferred mode blocks Apply for any error. Live mode retains the committed title/type when rejected, while accepted fields still apply. Rejected expressions are saved with an error and do not evaluate. Unlike `isColumnReferenceable`, unchanged expressions are revalidated on title/type edits and deferred Apply. Opening an editable live dialog also revalidates the expression, saving new rejections or clearing resolved ones. Not called during cell evaluation, when setting definitions, or when restoring state. Call `api.refreshCalculatedColumns()` to explicitly revalidate committed expressions. |
| `suppressColumnHighlighting` | `boolean` |  | `false` | Suppress highlighting the calculated column currently being edited by the dialog. |
| `applyMode` | `CalculatedColumnApplyMode` |  | `'live'` | When Calculated Column dialog edits are applied: `'live'` applies every change immediately; `'deferred'` validates the expression and applies changes via Apply and Cancel buttons. |
| `applyDebounce` | `number` |  | `0` | Quiet period in milliseconds before live dialog edits update the column and raise change events. Must be a non-negative integer. `0` applies on the next animation frame. Closing the dialog applies any pending edit immediately. Ignored in `'deferred'` apply mode. |
| `processColDef` | `Function` |  |  | Customise the Column Definition of calculated columns created in the Calculated Column dialog, for example to add a filter, enable aggregation or set a value formatter. Not called for calculated columns defined in `columnDefs`. Called every time the column is built. Return the full Column Definition to use; returning `params.colDef` or `undefined` leaves the column unchanged. Spread `params.colDef` to preserve the user's settings: omitted properties use normal column defaults, except `calculatedExpression`, which retains the user's expression. The returned definition takes precedence over `defaultColDef` and column types. `colId`, `editable`, `suppressPaste`, `field`, `valueGetter`, `valueSetter`, `cellEditor` and `cellEditorSelector` are always overridden for calculated columns. In `'live'` `applyMode` it is also called while the user types in the dialog, so keep it cheap and use `params.expressionValidation` to check the expression's validation status. `calculatedColumnReadOnly` takes effect after the initial Add dialog closes, including any pending live edit. In `'live'` `applyMode`, a returned `calculatedColumnReadOnly: true` locks the column when the Add dialog closes, even if its expression is empty or unfinished. The returned definition is not shown in the dialog and is not saved in grid state. Stateful properties such as `width`, `sort` and `hide` apply when the column is created; prefer the `initial*` forms. Replacing this callback rebuilds the columns; replacing only the containing options object does not. Do not call column APIs from this callback. |

### Column Properties

| Property | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `calculatedExpression` | `string` |  |  | Expression used to calculate this column's value from other columns in the same row. Use bracket references to read other columns by `colId`, e.g. `[revenue] - [cost]`. Calculated cells cannot be edited directly. [Module](https://www.ag-grid.com/archive/36.3.0/javascript-data-grid/modules/): `CalculatedColumnsModule`. |

### Grid API

These methods are explained in [Opening the Dialog](https://www.ag-grid.com/archive/36.3.0/javascript-data-grid/calculated-columns-dialog/#opening-the-dialog) and [Refreshing Calculated Columns](https://www.ag-grid.com/archive/36.3.0/javascript-data-grid/calculated-columns-validation/#refreshing-calculated-columns).

| Property | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `openCalculatedColumnDialog` | `Function` |  |  | Opens and focuses the dialog for an existing calculated column. Read-only calculated columns open in view mode. Reuses and brings forward an already-open dialog. On close, returns focus to the column's header, scrolling it into view if needed. Does nothing if Calculated Columns are disabled or the key does not resolve to a calculated column. [Module](https://www.ag-grid.com/archive/36.3.0/javascript-data-grid/modules/): `CalculatedColumnsModule`. |
| `openAddCalculatedColumnDialog` | `Function` |  |  | Opens the Add Calculated Column dialog, optionally after a source column as with the column menu. Without a source column, the new column is appended. Unknown or non-primary source columns are ignored. Uses the configured apply mode: live creates the column immediately, deferred creates it on Apply. Moves focus into the dialog. Does not open in pivot mode. On close, returns focus to the new column's header, or the source column if no column was created. [Module](https://www.ag-grid.com/archive/36.3.0/javascript-data-grid/modules/): `CalculatedColumnsModule`. |

| Property | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `refreshCalculatedColumns` | `Function` |  |  | Revalidate committed calculated expressions against the current `getValidationErrors` and `isColumnReferenceable` callbacks, update saved errors and recalculate values and dependants. Applies to read-only columns too. Does not apply pending dialog edits. Empty expressions are allowed. Open dialogs revalidate their drafts without restarting pending live updates. Validation changes raise `calculatedColumnChanged` with `source: 'api'`. [Module](https://www.ag-grid.com/archive/36.3.0/javascript-data-grid/modules/): `CalculatedColumnsModule`. |

### Grid Events

| Property | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `calculatedColumnCreated` | `CalculatedColumnCreatedEvent` |  |  | A calculated column has been created. |
| `calculatedColumnChanged` | `CalculatedColumnChangedEvent` |  |  | A calculated column's title, data type, expression, group visibility or expression validation has changed. Fires once per committed update, not during initialisation or Grid State restoration. Live updates can contain incomplete or invalid expressions. |
| `calculatedColumnRemoved` | `CalculatedColumnRemovedEvent` |  |  | A calculated column has been removed. |
| `calculatedColumnValidationStateChanged` | `CalculatedColumnValidationStateChangedEvent` |  |  | A calculated column expression has changed between valid and invalid. |
