---
title: "Calculated Columns"
enterprise: true
framework: react
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`.

```jsx
const calculatedColumns = true;
const [columnDefs, setColumnDefs] = useState([
    { field: 'revenue' },
    { field: 'cost' },
    {
        colId: 'profit',
        calculatedExpression: '[revenue] - [cost]',
        cellDataType: 'number',
        sortable: true,
        filter: true,
    },
]);

<AgGridReact
    calculatedColumns={calculatedColumns}
    columnDefs={columnDefs}
/>
```

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

#### Calculated Columns

```tsx
"use client";

import React, {
  useCallback,
  useMemo,
  useRef,
  useState,
  StrictMode,
} from "react";
import { createRoot } from "react-dom/client";
import { AgGridReact, AgGridProvider } from "ag-grid-react";
import {
  CalculatedColumnsGridOption,
  ClientSideRowModelModule,
  ColDef,
  ColGroupDef,
  DataTypeDefinitions,
  GridOptions,
  ModuleRegistry,
  NumberEditorModule,
  NumberFilterModule,
  ValueFormatterLiteParams,
  enableDevValidations,
} from "ag-grid-community";
import { CalculatedColumnsModule, ColumnMenuModule } from "ag-grid-enterprise";
import { SalesRow } from "./interfaces";

if (process.env.NODE_ENV !== "production") {
  enableDevValidations();
}

const modules = [
  ClientSideRowModelModule,
  CalculatedColumnsModule,
  ColumnMenuModule,
  NumberEditorModule,
  NumberFilterModule,
];

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 GridExample = () => {
  const containerStyle = useMemo(() => ({ width: "100%", height: "100%" }), []);
  const gridStyle = useMemo(() => ({ height: "100%", width: "100%" }), []);
  const [rowData, setRowData] = useState<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 [columnDefs, setColumnDefs] = useState<ColDef[]>([
    { 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 dataTypeDefinitions = useMemo<DataTypeDefinitions>(() => {
    return {
      currency: {
        baseDataType: "number",
        extendsDataType: "number",
        valueFormatter: currencyFormatter,
      },
    };
  }, []);
  const calculatedColumns = useMemo<CalculatedColumnsGridOption>(() => {
    return {
      dataTypes: ["currency", "number", "text", "boolean"],
    };
  }, []);
  const defaultColDef = useMemo<ColDef>(() => {
    return {
      flex: 1,
      minWidth: 130,
    };
  }, []);

  return (
    <AgGridProvider modules={modules}>
      <div style={containerStyle}>
        <div style={{ height: "100%", boxSizing: "border-box" }}>
          <div style={gridStyle}>
            <AgGridReact<SalesRow>
              rowData={rowData}
              columnDefs={columnDefs}
              dataTypeDefinitions={dataTypeDefinitions}
              calculatedColumns={calculatedColumns}
              defaultColDef={defaultColDef}
            />
          </div>
        </div>
      </div>
    </AgGridProvider>
  );
};

const root = createRoot(document.getElementById("root")!);
root.render(
  <StrictMode>
    <GridExample />
  </StrictMode>,
);
```

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

Register [Cell Data Types](https://www.ag-grid.com/react-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/react-data-grid/grouping/), [Tree Data](https://www.ag-grid.com/react-data-grid/tree-data/), [Sorting](https://www.ag-grid.com/react-data-grid/row-sorting/) and [Filtering](https://www.ag-grid.com/react-data-grid/filtering/), [Text Formatting](https://www.ag-grid.com/react-data-grid/value-formatters/) and [Cell Components](https://www.ag-grid.com/react-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/react-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

```tsx
"use client";

import React, {
  useCallback,
  useMemo,
  useRef,
  useState,
  StrictMode,
} from "react";
import { createRoot } from "react-dom/client";
import { AgGridReact, AgGridProvider } from "ag-grid-react";
import {
  AutoGroupColumnDef,
  CalculatedColumnsGridOption,
  ClientSideRowModelModule,
  ColDef,
  ColGroupDef,
  DataTypeDefinitions,
  GridOptions,
  ModuleRegistry,
  NumberFilterModule,
  ValueFormatterLiteParams,
  enableDevValidations,
} from "ag-grid-community";
import {
  CalculatedColumnsModule,
  ColumnMenuModule,
  RowGroupingModule,
} from "ag-grid-enterprise";
import { SalesRow } from "./interfaces";

if (process.env.NODE_ENV !== "production") {
  enableDevValidations();
}

const modules = [
  ClientSideRowModelModule,
  CalculatedColumnsModule,
  ColumnMenuModule,
  RowGroupingModule,
  NumberFilterModule,
];

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 GridExample = () => {
  const containerStyle = useMemo(() => ({ width: "100%", height: "100%" }), []);
  const gridStyle = useMemo(() => ({ height: "100%", width: "100%" }), []);
  const [rowData, setRowData] = useState<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 [columnDefs, setColumnDefs] = useState<ColDef[]>([
    { 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 dataTypeDefinitions = useMemo<DataTypeDefinitions>(() => {
    return {
      currency: {
        baseDataType: "number",
        extendsDataType: "number",
        valueFormatter: currencyFormatter,
      },
      percentage: {
        baseDataType: "number",
        extendsDataType: "number",
        valueFormatter: percentageFormatter,
      },
    };
  }, []);
  const calculatedColumns = useMemo<CalculatedColumnsGridOption>(() => {
    return {
      dataTypes: ["currency", "percentage", "number", "text", "boolean"],
    };
  }, []);
  const defaultColDef = useMemo<ColDef>(() => {
    return {
      flex: 1,
      minWidth: 130,
    };
  }, []);
  const autoGroupColumnDef = useMemo<AutoGroupColumnDef>(() => {
    return {
      headerName: "Product Type",
      minWidth: 180,
    };
  }, []);

  return (
    <AgGridProvider modules={modules}>
      <div style={containerStyle}>
        <div style={{ height: "100%", boxSizing: "border-box" }}>
          <div style={gridStyle}>
            <AgGridReact<SalesRow>
              rowData={rowData}
              columnDefs={columnDefs}
              dataTypeDefinitions={dataTypeDefinitions}
              calculatedColumns={calculatedColumns}
              defaultColDef={defaultColDef}
              autoGroupColumnDef={autoGroupColumnDef}
              groupDefaultExpanded={-1}
            />
          </div>
        </div>
      </div>
    </AgGridProvider>
  );
};

const root = createRoot(document.getElementById("root")!);
root.render(
  <StrictMode>
    <GridExample />
  </StrictMode>,
);
```

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

## 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

```tsx
"use client";

import React, {
  useCallback,
  useMemo,
  useRef,
  useState,
  StrictMode,
} from "react";
import { createRoot } from "react-dom/client";
import { AgGridReact, AgGridProvider } from "ag-grid-react";
import {
  CalculatedColumnsGridOption,
  ClientSideRowModelModule,
  ColDef,
  ColGroupDef,
  DataTypeDefinitions,
  GridOptions,
  ModuleRegistry,
  NumberFilterModule,
  ValueFormatterLiteParams,
  enableDevValidations,
} from "ag-grid-community";
import { CalculatedColumnsModule, ColumnMenuModule } from "ag-grid-enterprise";
import { QuarterlyRevenueRow } from "./interfaces";

if (process.env.NODE_ENV !== "production") {
  enableDevValidations();
}

const modules = [
  ClientSideRowModelModule,
  CalculatedColumnsModule,
  ColumnMenuModule,
  NumberFilterModule,
];

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 GridExample = () => {
  const containerStyle = useMemo(() => ({ width: "100%", height: "100%" }), []);
  const gridStyle = useMemo(() => ({ height: "100%", width: "100%" }), []);
  const [rowData, setRowData] = useState<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 [columnDefs, setColumnDefs] = useState<(ColDef | ColGroupDef)[]>([
    { 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 dataTypeDefinitions = useMemo<DataTypeDefinitions>(() => {
    return {
      currency: {
        baseDataType: "number",
        extendsDataType: "number",
        valueFormatter: currencyFormatter,
      },
      percentage: {
        baseDataType: "number",
        extendsDataType: "number",
        valueFormatter: percentageFormatter,
      },
    };
  }, []);
  const calculatedColumns = useMemo<CalculatedColumnsGridOption>(() => {
    return {
      dataTypes: ["currency", "percentage", "number", "text", "boolean"],
    };
  }, []);
  const defaultColDef = useMemo<ColDef>(() => {
    return {
      minWidth: 120,
      flex: 1,
    };
  }, []);

  return (
    <AgGridProvider modules={modules}>
      <div style={containerStyle}>
        <div style={{ height: "100%", boxSizing: "border-box" }}>
          <div style={gridStyle}>
            <AgGridReact<QuarterlyRevenueRow>
              rowData={rowData}
              columnDefs={columnDefs}
              dataTypeDefinitions={dataTypeDefinitions}
              calculatedColumns={calculatedColumns}
              defaultColDef={defaultColDef}
            />
          </div>
        </div>
      </div>
    </AgGridProvider>
  );
};

const root = createRoot(document.getElementById("root")!);
root.render(
  <StrictMode>
    <GridExample />
  </StrictMode>,
);
```

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

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/react-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`:

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

<AgGridReact
    dataTypeDefinitions={dataTypeDefinitions}
    calculatedColumns={calculatedColumns}
    columnDefs={columnDefs}
/>
```

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/react-data-grid/cell-data-types/), the grid's normal `cellDataType` validation applies when the calculated column is created.

#### Dialog Data Types

```tsx
"use client";

import React, {
  useCallback,
  useMemo,
  useRef,
  useState,
  StrictMode,
} from "react";
import { createRoot } from "react-dom/client";
import { AgGridReact, AgGridProvider } from "ag-grid-react";
import {
  CalculatedColumnsGridOption,
  ClientSideRowModelModule,
  ColDef,
  ColGroupDef,
  DataTypeDefinitions,
  GridOptions,
  ModuleRegistry,
  NumberFilterModule,
  ValueFormatterLiteParams,
  enableDevValidations,
} from "ag-grid-community";
import { CalculatedColumnsModule, ColumnMenuModule } from "ag-grid-enterprise";
import { SalesRow } from "./interfaces";

if (process.env.NODE_ENV !== "production") {
  enableDevValidations();
}

const modules = [
  ClientSideRowModelModule,
  CalculatedColumnsModule,
  ColumnMenuModule,
  NumberFilterModule,
];

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 GridExample = () => {
  const containerStyle = useMemo(() => ({ width: "100%", height: "100%" }), []);
  const gridStyle = useMemo(() => ({ height: "100%", width: "100%" }), []);
  const [rowData, setRowData] = useState<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 [columnDefs, setColumnDefs] = useState<ColDef[]>([
    { 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 dataTypeDefinitions = useMemo<DataTypeDefinitions>(() => {
    return {
      currency: {
        baseDataType: "number",
        extendsDataType: "number",
        valueFormatter: currencyFormatter,
      },
    };
  }, []);
  const calculatedColumns = useMemo<CalculatedColumnsGridOption>(() => {
    return {
      dataTypes: ["currency", "number", "boolean"],
    };
  }, []);
  const defaultColDef = useMemo<ColDef>(() => {
    return {
      flex: 1,
      minWidth: 130,
    };
  }, []);

  return (
    <AgGridProvider modules={modules}>
      <div style={containerStyle}>
        <div style={{ height: "100%", boxSizing: "border-box" }}>
          <div style={gridStyle}>
            <AgGridReact<SalesRow>
              rowData={rowData}
              columnDefs={columnDefs}
              dataTypeDefinitions={dataTypeDefinitions}
              calculatedColumns={calculatedColumns}
              defaultColDef={defaultColDef}
            />
          </div>
        </div>
      </div>
    </AgGridProvider>
  );
};

const root = createRoot(document.getElementById("root")!);
root.render(
  <StrictMode>
    <GridExample />
  </StrictMode>,
);
```

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

### expressionPickers

Use `expressionPickers` to control which expression pickers appear:

```jsx
const calculatedColumns = {
    expressionPickers: ['columns'],
};

<AgGridReact calculatedColumns={calculatedColumns} />
```

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

```tsx
"use client";

import React, {
  useCallback,
  useMemo,
  useRef,
  useState,
  StrictMode,
} from "react";
import { createRoot } from "react-dom/client";
import { AgGridReact, AgGridProvider } from "ag-grid-react";
import {
  CalculatedColumnsGridOption,
  ClientSideRowModelModule,
  ColDef,
  ColGroupDef,
  DataTypeDefinitions,
  GridOptions,
  ModuleRegistry,
  NumberFilterModule,
  ValueFormatterLiteParams,
  enableDevValidations,
} from "ag-grid-community";
import { CalculatedColumnsModule, ColumnMenuModule } from "ag-grid-enterprise";
import { SalesRow } from "./interfaces";

if (process.env.NODE_ENV !== "production") {
  enableDevValidations();
}

const modules = [
  ClientSideRowModelModule,
  CalculatedColumnsModule,
  ColumnMenuModule,
  NumberFilterModule,
];

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 GridExample = () => {
  const containerStyle = useMemo(() => ({ width: "100%", height: "100%" }), []);
  const gridStyle = useMemo(() => ({ height: "100%", width: "100%" }), []);
  const [rowData, setRowData] = useState<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 [columnDefs, setColumnDefs] = useState<ColDef[]>([
    { 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 dataTypeDefinitions = useMemo<DataTypeDefinitions>(() => {
    return {
      currency: {
        baseDataType: "number",
        extendsDataType: "number",
        valueFormatter: currencyFormatter,
      },
    };
  }, []);
  const calculatedColumns = useMemo<CalculatedColumnsGridOption>(() => {
    return {
      expressionPickers: ["columns"],
      dataTypes: ["currency", "number", "text", "boolean"],
    };
  }, []);
  const defaultColDef = useMemo<ColDef>(() => {
    return {
      flex: 1,
      minWidth: 130,
    };
  }, []);

  return (
    <AgGridProvider modules={modules}>
      <div style={containerStyle}>
        <div style={{ height: "100%", boxSizing: "border-box" }}>
          <div style={gridStyle}>
            <AgGridReact<SalesRow>
              rowData={rowData}
              columnDefs={columnDefs}
              dataTypeDefinitions={dataTypeDefinitions}
              calculatedColumns={calculatedColumns}
              defaultColDef={defaultColDef}
            />
          </div>
        </div>
      </div>
    </AgGridProvider>
  );
};

const root = createRoot(document.getElementById("root")!);
root.render(
  <StrictMode>
    <GridExample />
  </StrictMode>,
);
```

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

### suppressColumnHighlighting

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

```jsx
const theme = myTheme;
const calculatedColumns = {
    suppressColumnHighlighting: true,
};

<AgGridReact
    theme={theme}
    calculatedColumns={calculatedColumns}
/>
```

#### Suppress Dialog Column Highlighting

```tsx
"use client";

import React, {
  useCallback,
  useMemo,
  useRef,
  useState,
  StrictMode,
} from "react";
import { createRoot } from "react-dom/client";
import { AgGridReact, AgGridProvider } from "ag-grid-react";
import {
  CalculatedColumnsGridOption,
  ClientSideRowModelModule,
  ColDef,
  ColGroupDef,
  DataTypeDefinitions,
  GridOptions,
  ModuleRegistry,
  NumberFilterModule,
  ValueFormatterLiteParams,
  enableDevValidations,
} from "ag-grid-community";
import { CalculatedColumnsModule, ColumnMenuModule } from "ag-grid-enterprise";
import { SalesRow } from "./interfaces";

if (process.env.NODE_ENV !== "production") {
  enableDevValidations();
}

const modules = [
  ClientSideRowModelModule,
  CalculatedColumnsModule,
  ColumnMenuModule,
  NumberFilterModule,
];

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 GridExample = () => {
  const containerStyle = useMemo(() => ({ width: "100%", height: "100%" }), []);
  const gridStyle = useMemo(() => ({ height: "100%", width: "100%" }), []);
  const [rowData, setRowData] = useState<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 [columnDefs, setColumnDefs] = useState<ColDef[]>([
    { 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 dataTypeDefinitions = useMemo<DataTypeDefinitions>(() => {
    return {
      currency: {
        baseDataType: "number",
        extendsDataType: "number",
        valueFormatter: currencyFormatter,
      },
    };
  }, []);
  const calculatedColumns = useMemo<CalculatedColumnsGridOption>(() => {
    return {
      suppressColumnHighlighting: true,
      dataTypes: ["currency", "number", "text", "boolean"],
    };
  }, []);
  const defaultColDef = useMemo<ColDef>(() => {
    return {
      flex: 1,
      minWidth: 130,
    };
  }, []);

  return (
    <AgGridProvider modules={modules}>
      <div style={containerStyle}>
        <div style={{ height: "100%", boxSizing: "border-box" }}>
          <div style={gridStyle}>
            <AgGridReact<SalesRow>
              rowData={rowData}
              columnDefs={columnDefs}
              dataTypeDefinitions={dataTypeDefinitions}
              calculatedColumns={calculatedColumns}
              defaultColDef={defaultColDef}
            />
          </div>
        </div>
      </div>
    </AgGridProvider>
  );
};

const root = createRoot(document.getElementById("root")!);
root.render(
  <StrictMode>
    <GridExample />
  </StrictMode>,
);
```

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

### 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:

```jsx
const calculatedColumns = {
    applyMode: 'deferred',
};

<AgGridReact calculatedColumns={calculatedColumns} />
```

```ts

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

#### Deferred Apply Mode

```tsx
"use client";

import React, {
  useCallback,
  useMemo,
  useRef,
  useState,
  StrictMode,
} from "react";
import { createRoot } from "react-dom/client";
import { AgGridReact, AgGridProvider } from "ag-grid-react";
import {
  CalculatedColumnsGridOption,
  ClientSideRowModelModule,
  ColDef,
  ColGroupDef,
  DataTypeDefinitions,
  GridOptions,
  ModuleRegistry,
  NumberEditorModule,
  NumberFilterModule,
  ValueFormatterLiteParams,
  enableDevValidations,
} from "ag-grid-community";
import { CalculatedColumnsModule, ColumnMenuModule } from "ag-grid-enterprise";
import { SalesRow } from "./interfaces";

if (process.env.NODE_ENV !== "production") {
  enableDevValidations();
}

const modules = [
  ClientSideRowModelModule,
  CalculatedColumnsModule,
  ColumnMenuModule,
  NumberEditorModule,
  NumberFilterModule,
];

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 GridExample = () => {
  const containerStyle = useMemo(() => ({ width: "100%", height: "100%" }), []);
  const gridStyle = useMemo(() => ({ height: "100%", width: "100%" }), []);
  const [rowData, setRowData] = useState<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 [columnDefs, setColumnDefs] = useState<ColDef[]>([
    { 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 dataTypeDefinitions = useMemo<DataTypeDefinitions>(() => {
    return {
      currency: {
        baseDataType: "number",
        extendsDataType: "number",
        valueFormatter: currencyFormatter,
      },
    };
  }, []);
  const calculatedColumns = useMemo<CalculatedColumnsGridOption>(() => {
    return {
      applyMode: "deferred",
      dataTypes: ["currency", "number", "text", "boolean"],
    };
  }, []);
  const defaultColDef = useMemo<ColDef>(() => {
    return {
      flex: 1,
      minWidth: 130,
    };
  }, []);

  return (
    <AgGridProvider modules={modules}>
      <div style={containerStyle}>
        <div style={{ height: "100%", boxSizing: "border-box" }}>
          <div style={gridStyle}>
            <AgGridReact<SalesRow>
              rowData={rowData}
              columnDefs={columnDefs}
              dataTypeDefinitions={dataTypeDefinitions}
              calculatedColumns={calculatedColumns}
              defaultColDef={defaultColDef}
            />
          </div>
        </div>
      </div>
    </AgGridProvider>
  );
};

const root = createRoot(document.getElementById("root")!);
root.render(
  <StrictMode>
    <GridExample />
  </StrictMode>,
);
```

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

## Advanced Calculated Columns

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

```jsx
const calculatedColumns = true;
const [columnDefs, setColumnDefs] = useState([
    { 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',
    },
]);

<AgGridReact
    calculatedColumns={calculatedColumns}
    columnDefs={columnDefs}
/>
```

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

#### Advanced Calculated Columns

```tsx
"use client";

import React, {
  useCallback,
  useMemo,
  useRef,
  useState,
  StrictMode,
} from "react";
import { createRoot } from "react-dom/client";
import { AgGridReact, AgGridProvider } from "ag-grid-react";
import {
  CalculatedColumnsGridOption,
  ClientSideRowModelModule,
  ColDef,
  ColGroupDef,
  DataTypeDefinitions,
  GridOptions,
  ModuleRegistry,
  NumberFilterModule,
  TextFilterModule,
  ValueFormatterLiteParams,
  enableDevValidations,
} from "ag-grid-community";
import { CalculatedColumnsModule, ColumnMenuModule } from "ag-grid-enterprise";
import { SalesRow } from "./interfaces";

if (process.env.NODE_ENV !== "production") {
  enableDevValidations();
}

const modules = [
  ClientSideRowModelModule,
  CalculatedColumnsModule,
  ColumnMenuModule,
  NumberFilterModule,
  TextFilterModule,
];

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 GridExample = () => {
  const containerStyle = useMemo(() => ({ width: "100%", height: "100%" }), []);
  const gridStyle = useMemo(() => ({ height: "100%", width: "100%" }), []);
  const [rowData, setRowData] = useState<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 [columnDefs, setColumnDefs] = useState<ColDef[]>([
    { 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 dataTypeDefinitions = useMemo<DataTypeDefinitions>(() => {
    return {
      currency: {
        baseDataType: "number",
        extendsDataType: "number",
        valueFormatter: currencyFormatter,
      },
      percentage: {
        baseDataType: "number",
        extendsDataType: "number",
        valueFormatter: percentageFormatter,
      },
    };
  }, []);
  const calculatedColumns = useMemo<CalculatedColumnsGridOption>(() => {
    return {
      dataTypes: ["currency", "percentage", "number", "text", "boolean"],
    };
  }, []);
  const defaultColDef = useMemo<ColDef>(() => {
    return {
      flex: 1,
      minWidth: 130,
    };
  }, []);

  return (
    <AgGridProvider modules={modules}>
      <div style={containerStyle}>
        <div style={{ height: "100%", boxSizing: "border-box" }}>
          <div style={gridStyle}>
            <AgGridReact<SalesRow>
              rowData={rowData}
              columnDefs={columnDefs}
              dataTypeDefinitions={dataTypeDefinitions}
              calculatedColumns={calculatedColumns}
              defaultColDef={defaultColDef}
            />
          </div>
        </div>
      </div>
    </AgGridProvider>
  );
};

const root = createRoot(document.getElementById("root")!);
root.render(
  <StrictMode>
    <GridExample />
  </StrictMode>,
);
```

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

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

```tsx
("use client");

import React, {
  useCallback,
  useMemo,
  useRef,
  useState,
  StrictMode,
} from "react";
import { createRoot } from "react-dom/client";
import { AgGridReact, AgGridProvider } from "ag-grid-react";
import "./styles.css";
import {
  CalculatedColumnsGridOption,
  ClientSideRowModelModule,
  ColDef,
  ColGroupDef,
  ColumnApiModule,
  DataTypeDefinitions,
  GridApi,
  GridOptions,
  ModuleRegistry,
  NumberFilterModule,
  ValueFormatterLiteParams,
  enableDevValidations,
} from "ag-grid-community";
import { CalculatedColumnsModule } from "ag-grid-enterprise";
import { SalesRow } from "./interfaces";

if (process.env.NODE_ENV !== "production") {
  enableDevValidations();
}

const modules = [
  ClientSideRowModelModule,
  CalculatedColumnsModule,
  ColumnApiModule,
  NumberFilterModule,
];

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 GridExample = () => {
  const gridRef = useRef<AgGridReact<SalesRow>>(null);
  const containerStyle = useMemo(() => ({ width: "100%", height: "100%" }), []);
  const gridStyle = useMemo(() => ({ height: "100%", width: "100%" }), []);
  const [rowData, setRowData] = useState<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 [columnDefs, setColumnDefs] = useState<ColDef[]>([
    { field: "product", flex: 1 },
    { field: "revenue", cellDataType: "currency" },
    { field: "cost", cellDataType: "currency" },
    {
      colId: "profit",
      headerName: "Profit",
      calculatedExpression: "[revenue] - [cost]",
      cellDataType: "currency",
    },
  ]);
  const dataTypeDefinitions = useMemo<DataTypeDefinitions>(() => {
    return {
      currency: {
        baseDataType: "number",
        extendsDataType: "number",
        valueFormatter: currencyFormatter,
      },
      percentage: {
        baseDataType: "number",
        extendsDataType: "number",
        valueFormatter: percentFormatter,
      },
    };
  }, []);
  const defaultColDef = useMemo<ColDef>(() => {
    return {
      flex: 1,
      minWidth: 130,
    };
  }, []);

  // Get: read the calculated columns currently in the grid.
  const logCalculatedColumns = useCallback(() => {
    const calculatedColumns = (gridRef.current!.api.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.
  const addMarginColumn = useCallback(() => {
    const colDefs = gridRef.current!.api.getColumnDefs() ?? [];
    if (
      colDefs.some(
        (colDef) => "colId" in colDef && colDef.colId === "profitMargin",
      )
    ) {
      return;
    }
    gridRef.current!.api.setGridOption("columnDefs", [
      ...colDefs,
      marginColumn,
    ]);
  }, [marginColumn]);

  // Edit: change an existing calculated column's expression.
  const editMarginExpression = useCallback(() => {
    const colDefs = gridRef.current!.api.getColumnDefs() ?? [];
    gridRef.current!.api.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.
  const removeMarginColumn = useCallback(() => {
    const colDefs = gridRef.current!.api.getColumnDefs() ?? [];
    gridRef.current!.api.setGridOption(
      "columnDefs",
      colDefs.filter(
        (colDef) => !("colId" in colDef && colDef.colId === "profitMargin"),
      ),
    );
  }, []);

  return (
    <AgGridProvider modules={modules}>
      <div style={containerStyle}>
        <div className="example-wrapper">
          <div style={{ marginBottom: "1rem" }}>
            <button onClick={addMarginColumn}>Add Profit Margin</button>
            <button onClick={editMarginExpression}>Edit Profit Margin</button>
            <button onClick={removeMarginColumn}>Remove Profit Margin</button>
            <button onClick={logCalculatedColumns}>
              Log Calculated Columns
            </button>
          </div>

          <div style={gridStyle}>
            <AgGridReact<SalesRow>
              ref={gridRef}
              rowData={rowData}
              columnDefs={columnDefs}
              dataTypeDefinitions={dataTypeDefinitions}
              calculatedColumns={true}
              defaultColDef={defaultColDef}
            />
          </div>
        </div>
      </div>
    </AgGridProvider>
  );
};

const root = createRoot(document.getElementById("root")!);
root.render(
  <StrictMode>
    <GridExample />
  </StrictMode>,
);
```

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

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/react-data-grid/modules/). |

### Grid Options

| Property | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `calculatedColumns` | `CalculatedColumnsGridOption` |  |  | Enables and configures Calculated Columns. Module: [`CalculatedColumnsModule`](https://www.ag-grid.com/react-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. |
