---
title: "Calculated Columns"
enterprise: true
framework: javascript
version: "36.1.0"
---

# Calculated Columns

Calculated Columns let your end users add read-only values to the grid without storing those values in row data.

## Enabling Calculated Columns

Calculated columns are enabled by setting `calculatedColumns: true`, and setting `calculatedExpression` on a column definition. In source code, bracket references such as `[revenue]` resolve to the same-row value from the column with that `colId`.

```js
const gridOptions = {
    calculatedColumns: true,
    columnDefs: [
        { field: 'revenue' },
        { field: 'cost' },
        {
            colId: 'profit',
            calculatedExpression: '[revenue] - [cost]',
            cellDataType: 'number',
            sortable: true,
            filter: true,
        },
    ],

    // other grid options ...
}
```

> **Note**
>
> Application-declared calculated columns must define a `colId` explicitly.

#### Calculated Columns

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

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

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

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

const currencyFormatter = (
  params: ValueFormatterLiteParams<SalesRow, number>,
): string => {
  const { value } = params;
  if (value == null) {
    return "";
  }

  if (String(value).startsWith("#")) {
    return String(value);
  }

  return `$${value.toLocaleString()}`;
};

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

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 },
];

const gridOptions: GridOptions<SalesRow> = {
  columnDefs,
  rowData,
  dataTypeDefinitions: {
    currency: {
      baseDataType: "number",
      extendsDataType: "number",
      valueFormatter: currencyFormatter,
    },
  },
  calculatedColumns: {
    dataTypes: ["currency", "number", "text", "boolean"],
  },
  defaultColDef: {
    flex: 1,
    minWidth: 130,
  },
};

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

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

Register [Cell Data Types](https://www.ag-grid.com/javascript-data-grid/cell-data-types/) to give users formatting options to pick from when they create a column. The example above provides a `currency` type — see [dataTypes](#datatypes).

Calculated expressions do not need a leading equals sign (`=`). The value inside brackets must match a column `colId`, which defaults to the `field` when no explicit `colId` is provided.

Calculated Columns are supported with all row models. Same-row bracket references are reliable everywhere. Cross-row and range references depend on the referenced rows being loaded in the current row model.

For Server-Side, Infinite and Viewport Row Models, sorting and filtering are datasource or server responsibilities. The grid displays calculated values for loaded rows, but does not client-sort or client-filter remote datasets by calculated results.

Calculated columns are always read-only. They cannot be edited through cell editing, paste, fill handle or delete operations.

## Row Groups and Tree Data

Calculated columns are full columns, so [Row Grouping](https://www.ag-grid.com/javascript-data-grid/grouping/), [Tree Data](https://www.ag-grid.com/javascript-data-grid/tree-data/), [Sorting](https://www.ag-grid.com/javascript-data-grid/row-sorting/) and [Filtering](https://www.ag-grid.com/javascript-data-grid/filtering/), [Text Formatting](https://www.ag-grid.com/javascript-data-grid/value-formatters/) and [Cell Components](https://www.ag-grid.com/javascript-data-grid/component-cell-renderer/) apply to them the same way as any other column.

A calculated column evaluates on rows that have their own data: leaf rows, and Tree Data parents that carry data. It stays blank on rows without data — row group rows, group footers, the grand-total row, and Tree Data filler nodes.

To show a value on group rows, give the calculated column an `aggFunc`. Each leaf evaluates the expression, and the group aggregates those per-leaf results with the chosen function — the same as a column with a `valueGetter`. For example `{ calculatedExpression: '[revenue] - [cost]', aggFunc: 'sum' }` shows the total of its rows' profits on a group. A ratio has no meaningful aggregation, so a column like `[profit] / [revenue]` is left without an `aggFunc` and stays blank on group rows.

Under [Pivoting](https://www.ag-grid.com/javascript-data-grid/pivoting/), a calculated column with an `aggFunc` is a value column: it evaluates per leaf and its results aggregate into the pivot result columns, the same as a column with a `valueGetter`. Without an `aggFunc` it is not a value column, so it has no pivot result column and is absent from the cross-tab, like any other non-value column.

A calculated column can also be a pivot column (`pivot: true`): its per-row result becomes the pivot key, so the grid creates a result column for each distinct calculated value — the same as pivoting on a column with a `valueGetter`.

#### Row Groups with Calculated Columns

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

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

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

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

const formatter = (
  params: ValueFormatterLiteParams<SalesRow, number>,
  format: (value: number) => string,
): string => {
  const { value } = params;
  if (value == null) {
    return "";
  }

  if (String(value).startsWith("#")) {
    return String(value);
  }

  return format(value);
};

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

const percentageFormatter = (
  params: ValueFormatterLiteParams<SalesRow, number>,
) => formatter(params, (value) => `${Math.round(value * 100)}%`);

const columnDefs: ColDef<SalesRow>[] = [
  { field: "productType", rowGroup: true, hide: true },
  { field: "product", flex: 1.4 },
  {
    field: "revenue",
    aggFunc: "sum",
    cellDataType: "currency",
  },
  {
    field: "cost",
    aggFunc: "sum",
    cellDataType: "currency",
  },
  {
    colId: "profit",
    headerName: "Profit",
    calculatedExpression: "[revenue] - [cost]",
    // aggFunc lets the calculated column aggregate its per-leaf results onto group rows.
    aggFunc: "sum",
    cellDataType: "currency",
    filter: "agNumberColumnFilter",
  },
  {
    colId: "margin",
    headerName: "Margin",
    // No aggFunc: a ratio does not aggregate, so margin evaluates on leaf rows and is blank on groups.
    calculatedExpression: "[profit] / [revenue]",
    cellDataType: "percentage",
  },
];

const rowData: SalesRow[] = [
  {
    productType: "Solar",
    product: "Solar panel kit",
    revenue: 142000,
    cost: 96000,
  },
  {
    productType: "Solar",
    product: "Smart thermostat",
    revenue: 78000,
    cost: 52000,
  },
  {
    productType: "Charging",
    product: "Battery pack",
    revenue: 126000,
    cost: 101000,
  },
  {
    productType: "Charging",
    product: "EV charger",
    revenue: 92000,
    cost: 61000,
  },
  {
    productType: "Heating",
    product: "Heat pump",
    revenue: 168000,
    cost: 119000,
  },
  {
    productType: "Heating",
    product: "Hybrid boiler",
    revenue: 156000,
    cost: 117000,
  },
  {
    productType: "Heating",
    product: "Heat recovery unit",
    revenue: 124000,
    cost: 88000,
  },
  {
    productType: "Storage",
    product: "Storage cabinet",
    revenue: 71000,
    cost: 52000,
  },
  {
    productType: "Storage",
    product: "Lithium rack",
    revenue: 143000,
    cost: 112000,
  },
  {
    productType: "Storage",
    product: "Flow battery",
    revenue: 187000,
    cost: 149000,
  },
  {
    productType: "Storage",
    product: "Backup generator",
    revenue: 173000,
    cost: 131000,
  },
  {
    productType: "Wind",
    product: "Wind turbine kit",
    revenue: 232000,
    cost: 171000,
  },
  {
    productType: "Wind",
    product: "Micro turbine",
    revenue: 76000,
    cost: 49000,
  },
  { productType: "Wind", product: "Tower mount", revenue: 41000, cost: 26000 },
  {
    productType: "Monitoring",
    product: "Energy monitor",
    revenue: 47000,
    cost: 29000,
  },
  {
    productType: "Monitoring",
    product: "Smart meter",
    revenue: 39000,
    cost: 24000,
  },
  {
    productType: "Monitoring",
    product: "Grid analyser",
    revenue: 58000,
    cost: 36000,
  },
  {
    productType: "Efficiency",
    product: "Insulation pack",
    revenue: 44000,
    cost: 27000,
  },
  {
    productType: "Efficiency",
    product: "LED retrofit",
    revenue: 36000,
    cost: 21000,
  },
  {
    productType: "Efficiency",
    product: "Window film",
    revenue: 28000,
    cost: 16000,
  },
];

const gridOptions: GridOptions<SalesRow> = {
  columnDefs,
  rowData,
  dataTypeDefinitions: {
    currency: {
      baseDataType: "number",
      extendsDataType: "number",
      valueFormatter: currencyFormatter,
    },
    percentage: {
      baseDataType: "number",
      extendsDataType: "number",
      valueFormatter: percentageFormatter,
    },
  },
  calculatedColumns: {
    dataTypes: ["currency", "percentage", "number", "text", "boolean"],
  },
  defaultColDef: {
    flex: 1,
    minWidth: 130,
  },
  autoGroupColumnDef: {
    headerName: "Product Type",
    minWidth: 180,
  },
  groupDefaultExpanded: -1,
};

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

[Live example: Row Groups with Calculated Columns](https://www.ag-grid.com/examples/calculated-columns/calculated-columns-row-groups/typescript)

## References with Column Groups

Register the Column Menu so users can add a calculated column from the header menu with **Add Calculated Column**. The header menu on a calculated column shows **Edit Calculated Column** to change its title, type and expression, and **Remove Calculated Column** to remove it. Right-clicking cells in a calculated column also shows **Remove Calculated Column**.

The dialog shows references by header name, for example `[Revenue]`. When headers are duplicated under groups, it uses the shortest unique group path, such as `[2025 Q4]` and `[2026 Q4]`. It translates these display references back to `colId` references before updating the column definition.

If two columns share the same header *and* the same group hierarchy, the dialog falls back to appending the `colId`, for example `[2025 Q4 (revenue-q4)]`. This keeps the reference stable when columns are reordered. Give your columns distinct headers or group hierarchies to avoid this.

The following example has duplicate `Q1`, `Q2`, `Q3` and `Q4` headers under `2025` and `2026` column groups. The year groups start collapsed and show a calculated `Total` column; expanding a group reveals the quarter columns. The dialog shows grouped references such as `[2025 Q4]` and `[2026 Q4]`, while the source code stores stable `colId` references such as `[q4_2025]` and `[q4_2026]`.

#### Column Groups with Duplicate Headers

```ts
import {
  ClientSideRowModelModule,
  ColDef,
  ColGroupDef,
  GridOptions,
  ModuleRegistry,
  NumberFilterModule,
  ValueFormatterLiteParams,
  createGrid,
  enableDevValidations,
} from "ag-grid-community";
import { CalculatedColumnsModule, ColumnMenuModule } from "ag-grid-enterprise";
import { QuarterlyRevenueRow } from "./interfaces";

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

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

type QuarterlyRevenueRow = {
  product: string;
  q1_2025: number;
  q2_2025: number;
  q3_2025: number;
  q4_2025: number;
  q1_2026: number;
  q2_2026: number;
  q3_2026: number;
  q4_2026: number;
};

type QuarterField = Exclude<keyof QuarterlyRevenueRow, "product">;

const formatter = (
  params: ValueFormatterLiteParams<QuarterlyRevenueRow, number>,
  format: (value: number) => string,
): string => {
  const { value } = params;
  if (value == null) {
    return "";
  }

  if (String(value).startsWith("#")) {
    return String(value);
  }

  return format(value);
};

const currencyFormatter = (
  params: ValueFormatterLiteParams<QuarterlyRevenueRow, number>,
) => formatter(params, (value) => `$${value.toLocaleString()}`);

const percentageFormatter = (
  params: ValueFormatterLiteParams<QuarterlyRevenueRow, number>,
) =>
  formatter(
    params,
    (value) =>
      `${(value * 100).toLocaleString(undefined, { maximumFractionDigits: 1 })}%`,
  );

const quarterColumn = (
  field: QuarterField,
): ColDef<QuarterlyRevenueRow, number> => ({
  field,
  colId: field,
  headerName: field.slice(0, 2).toUpperCase(),
  columnGroupShow: "open",
  cellDataType: "currency",
});

const columnDefs: (
  | ColDef<QuarterlyRevenueRow>
  | ColGroupDef<QuarterlyRevenueRow>
)[] = [
  { field: "product", pinned: "left", minWidth: 180, flex: 1.4 },
  {
    headerName: "2025",
    openByDefault: false,
    children: [
      quarterColumn("q1_2025"),
      quarterColumn("q2_2025"),
      quarterColumn("q3_2025"),
      quarterColumn("q4_2025"),
      {
        colId: "total_2025",
        headerName: "Total",
        columnGroupShow: "closed",
        calculatedExpression: "[q1_2025] + [q2_2025] + [q3_2025] + [q4_2025]",
        cellDataType: "currency",
      },
    ],
  },
  {
    headerName: "2026",
    openByDefault: false,
    children: [
      quarterColumn("q1_2026"),
      quarterColumn("q2_2026"),
      quarterColumn("q3_2026"),
      quarterColumn("q4_2026"),
      {
        colId: "total_2026",
        headerName: "Total",
        columnGroupShow: "closed",
        calculatedExpression: "[q1_2026] + [q2_2026] + [q3_2026] + [q4_2026]",
        cellDataType: "currency",
      },
    ],
  },
  {
    headerName: "Change",
    children: [
      {
        colId: "q4Change",
        headerName: "Q4 Change",
        calculatedExpression: "([q4_2026] - [q4_2025]) / [q4_2025]",
        cellDataType: "percentage",
        sortable: true,
        filter: "agNumberColumnFilter",
      },
      {
        colId: "yearChange",
        headerName: "Year Change",
        calculatedExpression:
          "([q1_2026] + [q2_2026] + [q3_2026] + [q4_2026]) - ([q1_2025] + [q2_2025] + [q3_2025] + [q4_2025])",
        cellDataType: "currency",
        sortable: true,
        filter: "agNumberColumnFilter",
      },
    ],
  },
];

const rowData: QuarterlyRevenueRow[] = [
  {
    product: "Solar panel kit",
    q1_2025: 35000,
    q2_2025: 38000,
    q3_2025: 42000,
    q4_2025: 44000,
    q1_2026: 48000,
    q2_2026: 50000,
    q3_2026: 55000,
    q4_2026: 58000,
  },
  {
    product: "Smart thermostat",
    q1_2025: 18000,
    q2_2025: 20000,
    q3_2025: 22000,
    q4_2025: 25000,
    q1_2026: 24000,
    q2_2026: 26000,
    q3_2026: 27000,
    q4_2026: 31000,
  },
  {
    product: "Battery pack",
    q1_2025: 29000,
    q2_2025: 31000,
    q3_2025: 35000,
    q4_2025: 39000,
    q1_2026: 41000,
    q2_2026: 43000,
    q3_2026: 46000,
    q4_2026: 52000,
  },
  {
    product: "EV charger",
    q1_2025: 22000,
    q2_2025: 24000,
    q3_2025: 26000,
    q4_2025: 28000,
    q1_2026: 30000,
    q2_2026: 32000,
    q3_2026: 34000,
    q4_2026: 36000,
  },
  {
    product: "Heat pump",
    q1_2025: 36000,
    q2_2025: 38500,
    q3_2025: 41000,
    q4_2025: 43500,
    q1_2026: 46000,
    q2_2026: 48500,
    q3_2026: 51000,
    q4_2026: 53500,
  },
  {
    product: "Inverter unit",
    q1_2025: 21000,
    q2_2025: 22500,
    q3_2025: 24000,
    q4_2025: 25500,
    q1_2026: 27000,
    q2_2026: 28500,
    q3_2026: 30000,
    q4_2026: 31500,
  },
  {
    product: "Wind turbine kit",
    q1_2025: 52000,
    q2_2025: 55000,
    q3_2025: 58000,
    q4_2025: 61000,
    q1_2026: 64000,
    q2_2026: 67000,
    q3_2026: 70000,
    q4_2026: 73000,
  },
  {
    product: "Solar tile roof",
    q1_2025: 45000,
    q2_2025: 47800,
    q3_2025: 50600,
    q4_2025: 53400,
    q1_2026: 56200,
    q2_2026: 59000,
    q3_2026: 61800,
    q4_2026: 64600,
  },
  {
    product: "Power optimiser",
    q1_2025: 15000,
    q2_2025: 16100,
    q3_2025: 17200,
    q4_2025: 18300,
    q1_2026: 19400,
    q2_2026: 20500,
    q3_2026: 21600,
    q4_2026: 22700,
  },
  {
    product: "Charge controller",
    q1_2025: 12500,
    q2_2025: 13400,
    q3_2025: 14300,
    q4_2025: 15200,
    q1_2026: 16100,
    q2_2026: 17000,
    q3_2026: 17900,
    q4_2026: 18800,
  },
  {
    product: "Energy monitor",
    q1_2025: 11000,
    q2_2025: 11800,
    q3_2025: 12600,
    q4_2025: 13400,
    q1_2026: 14200,
    q2_2026: 15000,
    q3_2026: 15800,
    q4_2026: 16600,
  },
  {
    product: "Storage cabinet",
    q1_2025: 17000,
    q2_2025: 18200,
    q3_2025: 19400,
    q4_2025: 20600,
    q1_2026: 21800,
    q2_2026: 23000,
    q3_2026: 24200,
    q4_2026: 25400,
  },
  {
    product: "Microinverter",
    q1_2025: 14000,
    q2_2025: 15000,
    q3_2025: 16000,
    q4_2025: 17000,
    q1_2026: 18000,
    q2_2026: 19000,
    q3_2026: 20000,
    q4_2026: 21000,
  },
  {
    product: "Heat recovery unit",
    q1_2025: 28000,
    q2_2025: 29900,
    q3_2025: 31800,
    q4_2025: 33700,
    q1_2026: 35600,
    q2_2026: 37500,
    q3_2026: 39400,
    q4_2026: 41300,
  },
  {
    product: "Hybrid boiler",
    q1_2025: 34000,
    q2_2025: 36300,
    q3_2025: 38600,
    q4_2025: 40900,
    q1_2026: 43200,
    q2_2026: 45500,
    q3_2026: 47800,
    q4_2026: 50100,
  },
  {
    product: "Smart meter",
    q1_2025: 9000,
    q2_2025: 9700,
    q3_2025: 10400,
    q4_2025: 11100,
    q1_2026: 11800,
    q2_2026: 12500,
    q3_2026: 13200,
    q4_2026: 13900,
  },
  {
    product: "Insulation pack",
    q1_2025: 10500,
    q2_2025: 11250,
    q3_2025: 12000,
    q4_2025: 12750,
    q1_2026: 13500,
    q2_2026: 14250,
    q3_2026: 15000,
    q4_2026: 15750,
  },
  {
    product: "EV cable set",
    q1_2025: 7500,
    q2_2025: 8050,
    q3_2025: 8600,
    q4_2025: 9150,
    q1_2026: 9700,
    q2_2026: 10250,
    q3_2026: 10800,
    q4_2026: 11350,
  },
  {
    product: "Solar pump",
    q1_2025: 16000,
    q2_2025: 17150,
    q3_2025: 18300,
    q4_2025: 19450,
    q1_2026: 20600,
    q2_2026: 21750,
    q3_2026: 22900,
    q4_2026: 24050,
  },
  {
    product: "Backup generator",
    q1_2025: 40000,
    q2_2025: 42600,
    q3_2025: 45200,
    q4_2025: 47800,
    q1_2026: 50400,
    q2_2026: 53000,
    q3_2026: 55600,
    q4_2026: 58200,
  },
];

const gridOptions: GridOptions<QuarterlyRevenueRow> = {
  columnDefs,
  rowData,
  dataTypeDefinitions: {
    currency: {
      baseDataType: "number",
      extendsDataType: "number",
      valueFormatter: currencyFormatter,
    },
    percentage: {
      baseDataType: "number",
      extendsDataType: "number",
      valueFormatter: percentageFormatter,
    },
  },
  calculatedColumns: {
    dataTypes: ["currency", "percentage", "number", "text", "boolean"],
  },
  defaultColDef: {
    minWidth: 120,
    flex: 1,
  },
};

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

[Live example: Column Groups with Duplicate Headers](https://www.ag-grid.com/examples/calculated-columns/calculated-columns-column-groups/typescript)

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/javascript-data-grid/grid-state/), which captures them and recreates them on restore.

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

## Dialog Options

The Calculated Column dialog can be configured with the `calculatedColumns` grid option. Set `calculatedColumns: true` to enable the feature with default dialog options, or provide an options object to enable the feature and customise the dialog.

While a column's dialog is open, the grid highlights the column's header and cells. Customise the highlight colour with the `--ag-calculated-column-highlight-color` CSS variable.

### dataTypes

Use `dataTypes` to control which cell data types appear in the dialog type dropdown. The list can include built-in cell data types and custom types registered with `dataTypeDefinitions`:

```js
const gridOptions = {
    dataTypeDefinitions: {
        currency: {
            extendsDataType: 'number',
            baseDataType: 'number',
            valueFormatter: params =>
                params.value == null ? '' : `$${params.value.toLocaleString()}`,
        },
    },
    calculatedColumns: {
        dataTypes: ['currency', 'number', 'boolean'],
    },
    columnDefs: [
        { field: 'revenue', cellDataType: 'currency' },
        { field: 'cost', cellDataType: 'currency' },
        {
            colId: 'profit',
            headerName: 'Profit',
            calculatedExpression: '[revenue] - [cost]',
            cellDataType: 'currency',
        },
    ],

    // other grid options ...
}
```

Applying the type to your own columns as well means a user who picks **Currency** in the dialog gets the same formatting as the columns you declared. A `valueFormatter` on a column definition cannot be selected in the dialog, so it is not available to users.

Built-in types use the grid's locale text, while custom type names are converted to readable labels in the dialog. If a selected value does not match a registered [Cell Data Type](https://www.ag-grid.com/javascript-data-grid/cell-data-types/), the grid's normal `cellDataType` validation applies when the calculated column is created.

#### Dialog Data Types

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

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

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

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

const currencyFormatter = (
  params: ValueFormatterLiteParams<SalesRow, number>,
): string => {
  const { value } = params;
  if (value == null) {
    return "";
  }

  if (String(value).startsWith("#")) {
    return String(value);
  }

  return `$${value.toLocaleString()}`;
};

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

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 },
];

const gridOptions: GridOptions<SalesRow> = {
  columnDefs,
  rowData,
  dataTypeDefinitions: {
    currency: {
      baseDataType: "number",
      extendsDataType: "number",
      valueFormatter: currencyFormatter,
    },
  },
  calculatedColumns: {
    dataTypes: ["currency", "number", "boolean"],
  },
  defaultColDef: {
    flex: 1,
    minWidth: 130,
  },
};

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

[Live example: Dialog Data Types](https://www.ag-grid.com/examples/calculated-columns/calculated-columns-dialog-data-types/typescript)

### expressionPickers

Use `expressionPickers` to control which expression pickers appear:

```js
const gridOptions = {
    calculatedColumns: {
        expressionPickers: ['columns'],
    },

    // other grid options ...
}
```

```ts

type CalculatedColumnExpressionPicker = 
      'columns' 
    | 'functions' 
    | 'operators'
```

This only controls the expression picker buttons. Use an empty array or `null` to hide all picker buttons. Inline autocomplete while typing in the expression editor remains available.

#### Dialog Expression Pickers

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

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

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

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

const currencyFormatter = (
  params: ValueFormatterLiteParams<SalesRow, number>,
): string => {
  const { value } = params;
  if (value == null) {
    return "";
  }

  if (String(value).startsWith("#")) {
    return String(value);
  }

  return `$${value.toLocaleString()}`;
};

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

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 },
];

const gridOptions: GridOptions<SalesRow> = {
  columnDefs,
  rowData,
  dataTypeDefinitions: {
    currency: {
      baseDataType: "number",
      extendsDataType: "number",
      valueFormatter: currencyFormatter,
    },
  },
  calculatedColumns: {
    expressionPickers: ["columns"],
    dataTypes: ["currency", "number", "text", "boolean"],
  },
  defaultColDef: {
    flex: 1,
    minWidth: 130,
  },
};

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

[Live example: Dialog Expression Pickers](https://www.ag-grid.com/examples/calculated-columns/calculated-columns-dialog-helper-lists/typescript)

### suppressColumnHighlighting

Use `suppressColumnHighlighting` to disable the highlight while the dialog is open:

```js
const gridOptions = {
    theme: myTheme,
    calculatedColumns: {
        suppressColumnHighlighting: true,
    },

    // other grid options ...
}
```

#### Suppress Dialog Column Highlighting

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

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

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

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

const currencyFormatter = (
  params: ValueFormatterLiteParams<SalesRow, number>,
): string => {
  const { value } = params;
  if (value == null) {
    return "";
  }

  if (String(value).startsWith("#")) {
    return String(value);
  }

  return `$${value.toLocaleString()}`;
};

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

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 },
];

const gridOptions: GridOptions<SalesRow> = {
  columnDefs,
  rowData,
  dataTypeDefinitions: {
    currency: {
      baseDataType: "number",
      extendsDataType: "number",
      valueFormatter: currencyFormatter,
    },
  },
  calculatedColumns: {
    suppressColumnHighlighting: true,
    dataTypes: ["currency", "number", "text", "boolean"],
  },
  defaultColDef: {
    flex: 1,
    minWidth: 130,
  },
};

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

[Live example: Suppress Dialog Column Highlighting](https://www.ag-grid.com/examples/calculated-columns/calculated-columns-dialog-highlighting/typescript)

### applyMode

By default, dialog edits apply to the column immediately: **Add Calculated Column** creates the column and opens the dialog over it, and title, type and expression changes update the column as you type. Closing the dialog keeps the latest state; remove the column with **Remove Calculated Column**. An empty expression renders blank cells, and invalid or incomplete expressions render formula errors until fixed.

Set `applyMode: 'deferred'` to hold changes until you click **Apply** instead. In deferred mode the dialog shows Apply and Cancel buttons and validates the expression, so you cannot apply an invalid one:

```js
const gridOptions = {
    calculatedColumns: {
        applyMode: 'deferred',
    },

    // other grid options ...
}
```

```ts

type CalculatedColumnApplyMode = 'live' | 'deferred'
```

#### Deferred Apply Mode

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

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

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

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

const currencyFormatter = (
  params: ValueFormatterLiteParams<SalesRow, number>,
): string => {
  const { value } = params;
  if (value == null) {
    return "";
  }

  if (String(value).startsWith("#")) {
    return String(value);
  }

  return `$${value.toLocaleString()}`;
};

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

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 },
];

const gridOptions: GridOptions<SalesRow> = {
  columnDefs,
  rowData,
  dataTypeDefinitions: {
    currency: {
      baseDataType: "number",
      extendsDataType: "number",
      valueFormatter: currencyFormatter,
    },
  },
  calculatedColumns: {
    applyMode: "deferred",
    dataTypes: ["currency", "number", "text", "boolean"],
  },
  defaultColDef: {
    flex: 1,
    minWidth: 130,
  },
};

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

[Live example: Deferred Apply Mode](https://www.ag-grid.com/examples/calculated-columns/calculated-columns-apply-mode/typescript)

## Advanced Calculated Columns

Calculated columns can use the same [Mathematical Operators](https://www.ag-grid.com/javascript-data-grid/formula-reference/#mathematical-operators) and [Provided Functions](https://www.ag-grid.com/javascript-data-grid/formula-reference/#provided-functions) as [formulas](https://www.ag-grid.com/javascript-data-grid/formulas/), and can reference other calculated columns in the same row. This allows chained derived values such as profit, margin and status.

```js
const gridOptions = {
    calculatedColumns: true,
    columnDefs: [
        { field: 'revenue' },
        { field: 'cost' },
        {
            colId: 'profit',
            calculatedExpression: '[revenue] - [cost]',
            cellDataType: 'currency',
        },
        {
            colId: 'margin',
            calculatedExpression: '[profit] / [revenue]',
            cellDataType: 'percentage',
        },
        {
            colId: 'status',
            calculatedExpression: 'IF([margin] >= 0.25, "Healthy", "Review")',
            cellDataType: 'text',
        },
    ],

    // other grid options ...
}
```

The `currency` and `percentage` types are registered with `dataTypeDefinitions`, as shown in [dataTypes](#datatypes).

#### Advanced Calculated Columns

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

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

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

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

const formatter = (
  params: ValueFormatterLiteParams<SalesRow, number>,
  format: (value: number) => string,
): string => {
  const { value } = params;
  if (value == null) {
    return "";
  }

  if (String(value).startsWith("#")) {
    return String(value);
  }

  return format(value);
};

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

const percentageFormatter = (
  params: ValueFormatterLiteParams<SalesRow, number>,
) => formatter(params, (value) => `${Math.round(value * 100)}%`);

const columnDefs: ColDef<SalesRow>[] = [
  { field: "account", flex: 1.4 },
  {
    field: "revenue",
    cellDataType: "currency",
  },
  {
    field: "cost",
    cellDataType: "currency",
  },
  {
    colId: "profit",
    headerName: "Profit",
    calculatedExpression: "[revenue] - [cost]",
    cellDataType: "currency",
    sortable: true,
    filter: "agNumberColumnFilter",
  },
  {
    colId: "margin",
    headerName: "Margin",
    calculatedExpression: "[profit] / [revenue]",
    cellDataType: "percentage",
    sortable: true,
    filter: "agNumberColumnFilter",
  },
  {
    colId: "status",
    headerName: "Status",
    calculatedExpression: 'IF([margin] >= 0.25, "Healthy", "Review")',
    cellDataType: "text",
    sortable: true,
    filter: "agTextColumnFilter",
  },
];

const rowData: SalesRow[] = [
  { account: "Northwind Energy", revenue: 245000, cost: 172000 },
  { account: "Summit Retail", revenue: 186000, cost: 151000 },
  { account: "Pioneer Logistics", revenue: 214000, cost: 139000 },
  { account: "Apex Manufacturing", revenue: 198000, cost: 158000 },
  { account: "Blue River Telecom", revenue: 276000, cost: 192000 },
  { account: "Crestline Foods", revenue: 167000, cost: 121000 },
  { account: "Harbor Freight Co", revenue: 142000, cost: 99000 },
  { account: "Atlas Mining", revenue: 251000, cost: 197000 },
  { account: "Veridian Health", revenue: 173000, cost: 128000 },
  { account: "Quantum Software", revenue: 298000, cost: 176000 },
  { account: "Redwood Hotels", revenue: 132000, cost: 104000 },
  { account: "Ironbridge Steel", revenue: 221000, cost: 183000 },
  { account: "Lakeside Media", revenue: 96000, cost: 71000 },
  { account: "Polar Shipping", revenue: 188000, cost: 142000 },
  { account: "Granite Insurance", revenue: 204000, cost: 149000 },
  { account: "Cobalt Mining", revenue: 243000, cost: 186000 },
  { account: "Meridian Airlines", revenue: 312000, cost: 268000 },
  { account: "Oakfield Farms", revenue: 87000, cost: 62000 },
  { account: "Silverline Bank", revenue: 265000, cost: 191000 },
  { account: "Horizon Telecom", revenue: 154000, cost: 112000 },
];

const gridOptions: GridOptions<SalesRow> = {
  columnDefs,
  rowData,
  dataTypeDefinitions: {
    currency: {
      baseDataType: "number",
      extendsDataType: "number",
      valueFormatter: currencyFormatter,
    },
    percentage: {
      baseDataType: "number",
      extendsDataType: "number",
      valueFormatter: percentageFormatter,
    },
  },
  calculatedColumns: {
    dataTypes: ["currency", "percentage", "number", "text", "boolean"],
  },
  defaultColDef: {
    flex: 1,
    minWidth: 130,
  },
};

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

[Live example: Advanced Calculated Columns](https://www.ag-grid.com/examples/calculated-columns/calculated-columns-advanced/typescript)

Header cells and grid cells for calculated columns include the `ag-calculated-column` CSS class; use it for custom styling.

A column added from the header menu appears after the column it was created from.

## API

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`.

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()`.

The following example adds, edits and removes a Profit Margin column through these APIs, and logs the calculated columns currently in the grid:

#### Calculated Columns API

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

// Enable extended validations only for development
if (process.env.NODE_ENV !== "production") {
  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,
  defaultColDef: {
    flex: 1,
    minWidth: 130,
  },
};

// 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/examples/calculated-columns/calculated-columns-api/typescript)

Calculated Column events also use stored `colId` references in their expression payloads. For example, an event raised from the dialog still reports `[revenue]`, not `[Revenue]`.

### 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 columns are read-only. Module: [`CalculatedColumnsModule`](https://www.ag-grid.com/javascript-data-grid/modules/). |

### Grid Options

| Property | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `calculatedColumns` | `CalculatedColumnsGridOption` |  |  | Enables and configures Calculated Columns. Module: [`CalculatedColumnsModule`](https://www.ag-grid.com/javascript-data-grid/modules/). |

### Events

| Property | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `calculatedColumnCreated` | `CalculatedColumnCreatedEvent` |  |  | A calculated column has been created. |
| `calculatedColumnExpressionChanged` | `CalculatedColumnExpressionChangedEvent` |  |  | A calculated column expression has changed. |
| `calculatedColumnRemoved` | `CalculatedColumnRemovedEvent` |  |  | A calculated column has been removed. |
| `calculatedColumnValidationStateChanged` | `CalculatedColumnValidationStateChangedEvent` |  |  | A calculated column expression has changed between valid and invalid. |
