---
product: "AG Grid"
title: "Calculated Columns - Dialog & Customisation"
description: "Configure the dialog that React Data Grid users create and edit calculated columns in: opening it, column references, the expression editor, data types, when edits apply, and customising the columns users create."
enterprise: true
framework: react
version: "36.3.0"
related:
    - title: "Restrictions & Validation"
      url: "https://www.ag-grid.com/archive/36.3.0/react-data-grid/calculated-columns-validation/"
    - title: "API, Events & State"
      url: "https://www.ag-grid.com/archive/36.3.0/react-data-grid/calculated-columns-api/"
llms: "https://www.ag-grid.com/archive/36.3.0/llms.txt"
---

# Calculated Columns - Dialog & Customisation

Configure the dialog users create and edit [calculated columns](https://www.ag-grid.com/archive/36.3.0/react-data-grid/calculated-columns/) in, and customise the columns they create.

The Calculated Column dialog has three fields: Title, Type and Expression. Configure the dialog with the `calculatedColumns` grid option. Set it to `true` to enable the feature with the default dialog, or to an options object to enable the feature and customise the dialog.

## Opening the Dialog

Users open the dialog from the grid's menus; see [Letting Users Add Columns](https://www.ag-grid.com/archive/36.3.0/react-data-grid/calculated-columns/#letting-users-add-columns). To open it from your own interface, call one of these grid API methods:

- `openCalculatedColumnDialog(column)` to edit or view an existing calculated column, bringing its dialog forward if already open.
- `openAddCalculatedColumnDialog(sourceColumn?)` to add one after a source column, or at the end when omitted.

Both methods focus the dialog and use the configured [apply mode](https://www.ag-grid.com/archive/36.3.0/react-data-grid/calculated-columns-dialog/#applymode). Adding is unavailable in pivot mode.

```javascript
// Call from an application button.
function onAddCalculatedColumn() {
    gridApi.openAddCalculatedColumnDialog();
}
```

Closing the dialog returns focus to the calculated column's header, scrolling it into view if needed. If a deferred Add was not applied, focus returns to the source column when supplied. Focus is not moved if you have already focused another element.

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

## Referencing Columns

The dialog shows references by header name, for example `[Revenue]`. It translates these display references back to `colId` references before updating the column definition. Some headers need more than the header name:

- Duplicate headers under column groups use the shortest unique group path, such as `[2025 Q4]` and `[2026 Q4]`.
- When two columns have the same header and the same group hierarchy, the dialog appends 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.
- A literal `]` in a header name is escaped as `]]`. For example, the header `Revenue [USD]` appears as `[Revenue [USD]]]`. The Columns picker inserts the escaped reference automatically; stored `colId` references are unchanged.

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") {
  // Enable extended validations only for development
  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/archive/36.3.0/examples/calculated-columns-dialog/calculated-columns-column-groups/reactFunctionalTs/)

## Expression Editor

The expression editor highlights references, functions and operators. Built-in errors mark the field invalid and have underlines, tooltips and accessible descriptions. Reference checks validate columns, not row availability. When validation runs, and what happens to invalid expressions, depends on the [apply mode](https://www.ag-grid.com/archive/36.3.0/react-data-grid/calculated-columns-dialog/#applymode).

### Expression Pickers

Buttons beside the expression open the Columns, Functions and Operators pickers. Use `expressionPickers` to control which pickers appear. By default all three do:

```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. The example in [Column Highlighting](https://www.ag-grid.com/archive/36.3.0/react-data-grid/calculated-columns-dialog/#suppresscolumnhighlighting) shows only the Columns picker.

## Column Highlighting

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.

Use `suppressColumnHighlighting` to turn the highlight off. The default is `false`:

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

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

The following example sets both `expressionPickers: ['columns']` and `suppressColumnHighlighting: true`:

#### Column Highlighting and 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") {
  // Enable extended validations only for development
  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 {
      // Show only the Columns picker beside the expression.
      expressionPickers: ["columns"],
      // Don't highlight the column while its dialog is open.
      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: Column Highlighting and Expression Pickers](https://www.ag-grid.com/archive/36.3.0/examples/calculated-columns-dialog/calculated-columns-dialog-highlighting/reactFunctionalTs/)

In the example, edit Profit. The dialog shows only the Columns picker, and the Profit column is not highlighted while the dialog is open. In the other examples on this page, the column being edited is highlighted.

## Data Types

Use `dataTypes` to control which cell data types appear in the dialog's Type dropdown. The default is `['text', 'number', 'date', 'boolean']`. 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}
/>
```

- Give your own columns the same types, as the snippet does for `revenue` and `cost`, so a user who picks Currency in the dialog gets the same formatting.
- 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/archive/36.3.0/react-data-grid/cell-data-types/), the grid's normal `cellDataType` validation applies when the calculated column is created.

#### 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") {
  // Enable extended validations only for development
  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: Data Types](https://www.ag-grid.com/archive/36.3.0/examples/calculated-columns-dialog/calculated-columns-dialog-data-types/reactFunctionalTs/)

In the example, edit Profit and open the Type dropdown. It lists Currency, Number and Boolean.

## Applying Changes

The `applyMode` option sets when dialog edits reach the column. The default is `'live'`:

|  | Live (default) | Deferred (`applyMode: 'deferred'`) |
| --- | --- | --- |
| Title, type and expression | Update the column as you type | Held until the user clicks Apply; the dialog shows Apply and Cancel buttons |
| Add Calculated Column | Creates the column and opens the dialog over it | Creates the column when the user clicks Apply |
| Invalid expressions | Still apply, and render formula errors in cells until fixed | Validated as you edit and on Apply, and not applied while invalid |

In live mode:

- Empty expressions remain valid.
- [Custom validation and reference restrictions](https://www.ag-grid.com/archive/36.3.0/react-data-grid/calculated-columns-validation/#how-rejected-edits-behave) still apply.
- Closing the dialog keeps the latest state; remove the column with **Remove Calculated Column**.
- Expression edits containing references excluded by [`isColumnReferenceable`](https://www.ag-grid.com/archive/36.3.0/react-data-grid/calculated-columns-validation/#iscolumnreferenceable) are saved but blocked from evaluation, preserving the full text for correction.

### Deferred Mode

Set `applyMode: 'deferred'` to hold changes until the user clicks Apply:

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

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

```ts

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

In deferred edit dialogs, validation runs as you edit and on Apply, not when the dialog opens.

#### Deferred 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") {
  // Enable extended validations only for development
  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 Mode](https://www.ag-grid.com/archive/36.3.0/examples/calculated-columns-dialog/calculated-columns-apply-mode/reactFunctionalTs/)

In the example, edit Profit and change its expression. The cells change only when you click **Apply**.

### Debouncing Live Edits

Set `applyDebounce` to wait for a quiet period before applying live dialog edits. This delays both the cell repaint and calculated-column change events:

```jsx
const calculatedColumns = {
    applyDebounce: 400,
};

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

- The default is `0`, which applies on the next animation frame.
- Closing the dialog applies any pending edit immediately; removing the column cancels it.
- State and column-definition APIs report the last applied values while an edit is pending.
- This option has no effect in deferred mode.

## Customising User-Created Columns

Use `processColDef` to customise calculated columns that users create in the dialog, for example to add a filter or allow aggregation. It receives the column's own definition, as set in the dialog, and returns the definition to use. The returned definition takes precedence over `defaultColDef` and column types:

```jsx
const calculatedColumns = {
    processColDef: (params) => ({
        ...params.colDef,
        filter: true,
        enableValue: params.colDef.cellDataType === 'number',
    }),
};

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

Spread `params.colDef` to preserve the user's settings. Omitted properties use normal column defaults, except `calculatedExpression`, which retains the user's expression.

In the example, the Profit column is restored from Grid State as if a user had created it, so it has a filter button and can be aggregated from the column menu.

#### Customising User-Created 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,
  GridOptions,
  GridState,
  GridStateModule,
  ModuleRegistry,
  NumberFilterModule,
  TextFilterModule,
  enableDevValidations,
} from "ag-grid-community";
import {
  CalculatedColumnsModule,
  ColumnMenuModule,
  RowGroupingModule,
} from "ag-grid-enterprise";
import { SalesRow } from "./interfaces";

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

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

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: "Wind",
      product: "Wind turbine kit",
      revenue: 232000,
      cost: 171000,
    },
    {
      productType: "Wind",
      product: "Micro turbine",
      revenue: 76000,
      cost: 49000,
    },
    {
      productType: "Monitoring",
      product: "Energy monitor",
      revenue: 47000,
      cost: 29000,
    },
    {
      productType: "Monitoring",
      product: "Smart meter",
      revenue: 39000,
      cost: 24000,
    },
    {
      productType: "Efficiency",
      product: "Insulation pack",
      revenue: 44000,
      cost: 27000,
    },
    {
      productType: "Efficiency",
      product: "LED retrofit",
      revenue: 36000,
      cost: 21000,
    },
  ]);
  const [columnDefs, setColumnDefs] = useState<ColDef[]>([
    { field: "productType", rowGroup: true, hide: true },
    { field: "product", flex: 1.4 },
    { field: "revenue", aggFunc: "sum" },
    { field: "cost", aggFunc: "sum" },
  ]);
  const calculatedColumns = useMemo<CalculatedColumnsGridOption>(() => {
    return {
      dataTypes: ["number", "text"],
      // Applied to every calculated column created in the dialog: add a filter, and allow
      // number columns to be aggregated from the column menu.
      processColDef: (params) => ({
        ...params.colDef,
        filter: true,
        enableValue: params.colDef.cellDataType === "number",
      }),
    };
  }, []);
  const initialState = useMemo<GridState>(() => {
    return {
      userColumns: [
        {
          colId: "profit",
          created: true,
          parentGroupId: null,
          properties: [
            { property: "headerName", value: "Profit" },
            { property: "calculatedExpression", value: "[revenue] - [cost]" },
            { property: "cellDataType", value: "number" },
          ],
        },
      ],
    };
  }, []);
  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}
              calculatedColumns={calculatedColumns}
              initialState={initialState}
              defaultColDef={defaultColDef}
              autoGroupColumnDef={autoGroupColumnDef}
              groupDefaultExpanded={-1}
            />
          </div>
        </div>
      </div>
    </AgGridProvider>
  );
};

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

[Live example: Customising User-Created Columns](https://www.ag-grid.com/archive/36.3.0/examples/calculated-columns-dialog/calculated-columns-process-col-def/reactFunctionalTs/)

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

| Property | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `colDef` | [`ColDef`](https://www.ag-grid.com/archive/36.3.0/react-data-grid/column-properties/) |  |  | The column's own definition: the properties set in the Calculated Column dialog, before `defaultColDef` and column types are merged in. A fresh copy is passed on every call. |
| `sourceColumn` | [`Column \| null`](https://www.ag-grid.com/archive/36.3.0/react-data-grid/column-object/) |  |  | The column the calculated column was added from, i.e. the one its menu was opened on. `null` when it was added from no column, or when that column is gone. A column restored from Grid State is not tied to the column it was added from: it gets the last column of its group, or `null` if it is not in a group. |
| `expressionValidation` | `CalculatedColumnExpressionValidation` |  |  | Validation of `colDef.calculatedExpression`, including syntax, direct column references and saved dialog rejections from `getValidationErrors` or `isColumnReferenceable`. Does not rerun application callbacks or check row-specific evaluation errors. In `'live'` apply mode, the expression may be incomplete. |
| `api` | [`GridApi`](https://www.ag-grid.com/archive/36.3.0/react-data-grid/grid-api/) |  |  | The grid api. |
| `context` | [`TContext`](https://www.ag-grid.com/archive/36.3.0/react-data-grid/typescript-generics/#context-tcontext) |  |  | Application context as set on `gridOptions.context`. |

`params.expressionValidation` includes syntax and reference errors plus saved dialog rejections from `getValidationErrors` and `isColumnReferenceable`, matching `expressionValidation` on the [calculatedColumnChanged](https://www.ag-grid.com/archive/36.3.0/react-data-grid/calculated-columns-api/#events) event. It does not rerun these callbacks or check row-specific evaluation errors.

### When the Callback Runs

- The callback runs each time the column is built, including when dialog edits are applied and on restore from [Grid State](https://www.ag-grid.com/archive/36.3.0/react-data-grid/grid-state/).
- It does not run for calculated columns declared in `columnDefs`.
- The dialog and Grid State keep the user's values, not the callback's.
- `colId`, `editable`, `suppressPaste`, `field`, `valueGetter`, `valueSetter`, `cellEditor` and `cellEditorSelector` are always overridden.
- Prefer `initial*` properties, such as `initialWidth`, to stateful ones.
- The definitions from `api.getColumnDefs()` include the callback's output; see [Managing Columns with Column Definitions](https://www.ag-grid.com/archive/36.3.0/react-data-grid/calculated-columns-api/#managing-columns-with-column-definitions).

### Making Created Columns Read-Only

`processColDef` can return `calculatedColumnReadOnly: true` to make user-created columns read-only. In live mode, `calculatedColumnReadOnly: true` locks the column when the initial Add dialog closes, even if the expression is empty or invalid.

To leave blank columns editable, return the full definition with the flag `true` only when there is an expression: `{ ...params.colDef, calculatedColumnReadOnly: !!params.colDef.calculatedExpression?.trim() }`.

A non-empty expression may still be unfinished; use `applyMode: 'deferred'` to require Apply before creating and locking the column.

See [Read-Only Calculated Columns](https://www.ag-grid.com/archive/36.3.0/react-data-grid/calculated-columns-validation/#read-only-calculated-columns) for what a read-only column allows.
