---
product: "AG Grid"
title: "Calculated Columns - Restrictions & Validation"
description: "Control what users can change in JavaScript Data Grid calculated columns: read-only columns, hidden menu items, restricted column references and custom validation of the Calculated Column dialog."
enterprise: true
framework: javascript
version: "36.3.0"
related:
    - title: "Dialog & Customisation"
      url: "https://www.ag-grid.com/javascript-data-grid/calculated-columns-dialog/"
    - title: "API, Events & State"
      url: "https://www.ag-grid.com/javascript-data-grid/calculated-columns-api/"
llms: "https://www.ag-grid.com/llms.txt"
---

# Calculated Columns - Restrictions & Validation

Control what users can change: make [calculated columns](https://www.ag-grid.com/javascript-data-grid/calculated-columns/) read-only, hide their menu items, restrict which columns they can reference and validate what users enter.

## Read-Only Calculated Columns

Set `calculatedColumnReadOnly: true` to prevent users from changing a column's title, type or expression in the dialog. Users can choose **View Calculated Column** to inspect its definition. **Remove Calculated Column** is disabled in the column and cell context menus.

```js
const gridOptions = {
    calculatedColumns: true,
    columnDefs: [
        { field: 'revenue' },
        { field: 'cost' },
        {
            colId: 'grossMargin',
            calculatedExpression: '([revenue] - [cost]) / [revenue]',
            cellDataType: 'number',
            calculatedColumnReadOnly: true,
        },
    ],

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

The example keeps Gross Margin read-only, while Profit and user-created calculated columns remain editable.

#### Read-Only Calculated Columns

```ts
import {
  ClientSideRowModelModule,
  ColDef,
  GridOptions,
  ModuleRegistry,
  createGrid,
  enableDevValidations,
} from "ag-grid-community";
import {
  CalculatedColumnsModule,
  ColumnMenuModule,
  ContextMenuModule,
} from "ag-grid-enterprise";

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

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

interface SalesRow {
  product: string;
  revenue: number;
  cost: number;
}

const columnDefs: ColDef<SalesRow>[] = [
  { field: "product" },
  { field: "revenue" },
  { field: "cost" },
  {
    colId: "grossMargin",
    headerName: "Gross Margin",
    calculatedExpression: "([revenue] - [cost]) / [revenue]",
    cellDataType: "number",
    calculatedColumnReadOnly: true,
    valueFormatter: ({ value }) =>
      typeof value === "number" ? `${(value * 100).toFixed(1)}%` : "",
  },
  {
    colId: "profit",
    headerName: "Profit",
    calculatedExpression: "[revenue] - [cost]",
    cellDataType: "number",
  },
];

const gridOptions: GridOptions<SalesRow> = {
  calculatedColumns: true,
  columnDefs,
  defaultColDef: { flex: 1, minWidth: 130 },
  rowData: [
    { 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 },
  ],
};

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

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

If a column becomes read-only while its edit dialog is open, the dialog closes and any pending edits are discarded. Changes already applied are kept.

> **Note**
>
> This setting restricts UI changes only: updating `columnDefs` and restoring [Grid State](https://www.ag-grid.com/javascript-data-grid/grid-state/) can still change or remove the column. Dialog-created columns ignore `defaultColDef.calculatedColumnReadOnly`, but [processColDef](https://www.ag-grid.com/javascript-data-grid/calculated-columns-dialog/#processcoldef) can make them read-only.

## Hiding Menu Items

The calculated column actions are built-in menu items. To stop users adding, editing or removing calculated columns from the menus, filter them out of the default items with [getColumnMenuItems](https://www.ag-grid.com/javascript-data-grid/column-menu/#customising-the-menu-items) and [getContextMenuItems](https://www.ag-grid.com/javascript-data-grid/context-menu/#configuring-the-context-menu):

- `calculatedColumn`: **Add Calculated Column**, in the column menu.
- `editCalculatedColumn`: **Edit Calculated Column**, or **View Calculated Column** when the column is read-only, in the column menu.
- `removeCalculatedColumn`: **Remove Calculated Column**, in the column menu and the cell context menu.

```js
const gridOptions = {
    calculatedColumns: true,
    getColumnMenuItems: ({ defaultItems }) =>
        defaultItems.filter(
            item => item !== 'editCalculatedColumn' && item !== 'removeCalculatedColumn'
        ),
    getContextMenuItems: ({ defaultItems }) =>
        defaultItems?.filter(item => item !== 'removeCalculatedColumn'),

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

As with [read-only columns](https://www.ag-grid.com/javascript-data-grid/calculated-columns-validation/#read-only-calculated-columns), this only removes the menu entries. Updating `columnDefs` and restoring Grid State can still change or remove the column.

## Restricting Column References

Return `false` from `isColumnReferenceable` to exclude a column from the Columns picker, inline autocomplete and newly entered references. Use `calculatedColumn` to identify the column being edited, or `sourceColumn` for the column whose menu initiated **Add Calculated Column**. `sourceColumn` is only provided when adding a column; it is `null` when editing an existing column.

This example allows Salary references only when editing Bonus:

```js
const gridOptions = {
    calculatedColumns: {
        isColumnReferenceable: ({ colDef, calculatedColumn }) => {
            if (colDef.field === 'salary') {
                return calculatedColumn?.getColId() === 'bonus';
            }
            return true;
        },
    },

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

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

| Property | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `column` | [`Column`](https://www.ag-grid.com/javascript-data-grid/column-object/) |  |  | The candidate source column. |
| `colDef` | [`ColDef`](https://www.ag-grid.com/javascript-data-grid/column-properties/) |  |  | The candidate source column's definition. |
| `calculatedColumn` | [`Column \| null`](https://www.ag-grid.com/javascript-data-grid/column-object/) |  |  | The column being edited, including a newly created column in live mode. `null` when adding in deferred mode. |
| `sourceColumn` | [`Column \| null`](https://www.ag-grid.com/javascript-data-grid/column-object/) |  |  | The column whose menu initiated Add Calculated Column. Only provided while adding a column, in either apply mode. `null` when editing an existing column or when there is no source column. |
| `api` | [`GridApi`](https://www.ag-grid.com/javascript-data-grid/grid-api/) |  |  | The grid api. |
| `context` | [`TContext`](https://www.ag-grid.com/javascript-data-grid/typescript-generics/#context-tcontext) |  |  | Application context as set on `gridOptions.context`. |

- The callback runs when the dialog opens.
- Existing permitted expressions can remain unchanged, and programmatic expressions are unrestricted unless explicitly revalidated with [`refreshCalculatedColumns()`](https://www.ag-grid.com/javascript-data-grid/calculated-columns-validation/#refreshing-calculated-columns).
- Restricted edits disable Apply in deferred mode. In live mode, they are saved but display `#REF!` until corrected; reopening the dialog or restoring Grid State preserves the error.

This is a UI restriction, not a security boundary.

#### Restricting Column References

```ts
import {
  ClientSideRowModelModule,
  GridOptions,
  ModuleRegistry,
  createGrid,
  enableDevValidations,
} from "ag-grid-community";
import { CalculatedColumnsModule, ColumnMenuModule } from "ag-grid-enterprise";

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

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

interface Employee {
  employee: string;
  salary: number;
  sales: number;
}

const gridOptions: GridOptions<Employee> = {
  columnDefs: [
    { field: "employee" },
    { field: "salary" },
    { field: "sales" },
    {
      colId: "bonus",
      headerName: "Bonus",
      calculatedExpression: "[salary] * 0.1",
      cellDataType: "number",
    },
    {
      colId: "target",
      headerName: "Sales Target",
      calculatedExpression: "[sales] * 1.2",
      cellDataType: "number",
    },
  ],
  rowData: [
    { employee: "Alex Morgan", salary: 50000, sales: 100000 },
    { employee: "Sam Taylor", salary: 60000, sales: 125000 },
    { employee: "Jamie Lee", salary: 55000, sales: 110000 },
  ],
  calculatedColumns: {
    isColumnReferenceable: ({ colDef, calculatedColumn }) => {
      if (colDef.field === "salary") {
        return calculatedColumn?.getColId() === "bonus";
      }
      return true;
    },
  },
  defaultColDef: { flex: 1, minWidth: 120 },
};

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

[Live example: Restricting Column References](https://www.ag-grid.com/examples/calculated-columns-validation/calculated-columns-reference-restrictions/typescript/)

In the example, edit Bonus and Sales Target. Only Bonus can reference Salary.

## Validating Dialog Fields

Use `getValidationErrors` to validate the dialog's title, type and expression. Return messages keyed by `headerName`, `cellDataType` or `calculatedExpression`, or `null` to accept the draft:

- Omitted fields and empty arrays are valid.
- Return or spread `internalErrors` to retain built-in feedback.
- The callback runs while typing, so keep it quick.
- Return translated messages; the grid shows them as you supply them.

```js
const gridOptions = {
    calculatedColumns: {
        applyMode: 'deferred',
        getValidationErrors: ({ colDef, internalErrors }) => ({
            ...internalErrors,
            headerName: (colDef.headerName?.length ?? 0) > 30
                ? ['Use a title of at most 30 characters.']
                : internalErrors?.headerName,
        }),
    },

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

In the example, edit Sales Bonus and enter a title longer than 30 characters or an expression using `[Salary]` to see field-specific errors.

#### Validating Dialog Fields

```ts
import {
  ClientSideRowModelModule,
  GridOptions,
  ModuleRegistry,
  createGrid,
  enableDevValidations,
} from "ag-grid-community";
import { CalculatedColumnsModule, ColumnMenuModule } from "ag-grid-enterprise";

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

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

interface Employee {
  employee: string;
  salary: number;
  sales: number;
}

const gridOptions: GridOptions<Employee> = {
  columnDefs: [
    { field: "employee" },
    { field: "salary" },
    { field: "sales" },
    {
      colId: "bonus",
      headerName: "Sales Bonus",
      calculatedExpression: "[sales] * 0.1",
      cellDataType: "number",
    },
  ],
  rowData: [
    { employee: "Alex Morgan", salary: 50000, sales: 100000 },
    { employee: "Sam Taylor", salary: 60000, sales: 125000 },
    { employee: "Jamie Lee", salary: 55000, sales: 110000 },
  ],
  calculatedColumns: {
    applyMode: "deferred",
    getValidationErrors: ({ colDef, referencedColumns, internalErrors }) => ({
      ...internalErrors,
      headerName:
        (colDef.headerName?.length ?? 0) > 30
          ? ["Use a title of at most 30 characters."]
          : internalErrors?.headerName,
      calculatedExpression: referencedColumns.some(
        (column) => column.getColId() === "salary",
      )
        ? [
            "Salary cannot be used in this report.",
            "Choose a sales-based expression.",
          ]
        : internalErrors?.calculatedExpression,
    }),
  },
  defaultColDef: { flex: 1, minWidth: 120 },
};

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

[Live example: Validating Dialog Fields](https://www.ag-grid.com/examples/calculated-columns-validation/calculated-columns-custom-validation/typescript/)

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

| Property | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `colDef` | `CalculatedColumnDef` |  |  | A copy of the draft definition, with recognised references converted to stored `[colId]` references. |
| `displayExpression` | `string` |  |  | The expression displayed in the dialog, using column header references. |
| `referencedColumns` | `readonly Column[]` |  |  | Unique columns directly referenced by the expression, including hidden columns. For invalid syntax, includes recognised complete bracket references. |
| `internalErrors` | `CalculatedColumnValidationErrors \| null` |  |  | Built-in validation errors, or `null`. Include these in the callback result to retain built-in validation. |
| `column` | [`Column \| null`](https://www.ag-grid.com/javascript-data-grid/column-object/) |  |  | The calculated column, or `null` when adding a column in deferred mode. |
| `sourceColumn` | [`Column \| null`](https://www.ag-grid.com/javascript-data-grid/column-object/) |  |  | The column whose menu initiated Add Calculated Column. Only provided while adding a column, in either apply mode. `null` when editing an existing column or when there is no source column. |
| `api` | [`GridApi`](https://www.ag-grid.com/javascript-data-grid/grid-api/) |  |  | The grid api. |
| `context` | [`TContext`](https://www.ag-grid.com/javascript-data-grid/typescript-generics/#context-tcontext) |  |  | Application context as set on `gridOptions.context`. |

Editing the title or type revalidates the expression too, even when the expression is unchanged. Opening an editable live dialog rechecks expression rejections, even without edits.

> **Note**
>
> This is a dialog guard, not a security boundary: `columnDefs` and Grid State restores do not run the callback. Rejections restored from Grid State remain blocked.

### How Rejected Edits Behave

Any field error blocks Apply in deferred mode. In live mode, the outcome depends on the field:

| Rejected edit | Deferred mode | Live mode |
| --- | --- | --- |
| Title or type rejected by `getValidationErrors` | Blocks Apply | Keeps its last applied value, while other fields apply |
| Expression rejected by `getValidationErrors` | Blocks Apply | Saved, and displays `#ERROR!`, including when the callback returns built-in expression errors |
| Expression referencing a column restricted by `isColumnReferenceable` | Disables Apply | Saved, and displays `#REF!` until corrected. Reference restrictions remain enforced, and use `#REF!` instead of `#ERROR!`, even when `getValidationErrors` is set |

## Refreshing Calculated Columns

Call `refreshCalculatedColumns()` after application validation rules change to revalidate expressions, update saved errors and recalculate cells and dependants without opening the dialog.

```js
api.refreshCalculatedColumns({ columns: ['profit'] });
// Omit columns to refresh all calculated columns.
api.refreshCalculatedColumns();
```

The refresh explicitly applies the current `getValidationErrors` and `isColumnReferenceable` rules:

- It includes read-only and programmatically defined columns.
- Applied expressions are checked with `sourceColumn: null`.
- Removed rules clear their saved rejections.
- Open dialogs also revalidate their drafts, without applying them or restarting the debounce.
- Validation changes emit `calculatedColumnChanged` with `source: 'api'`.

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