Standard Validation Copy Link
The Grid provides built-in validation for all Provided Cell Editors, such as the Text, Large Text, Number and Date editors. These editors support validation automatically by checking the constraints defined in the column configuration. For example:
TextandLarge Texteditors will respect themaxLengthproperty.Numbereditors validate against min and max constraints.Dateeditors ensure the value is a valid date string.
Validation is performed when editing ends, and the Grid will handle invalid values based on the selected Validation Modes.
import {
ClientSideRowModelApiModule,
ClientSideRowModelModule,
DateEditorModule,
GridApi,
GridOptions,
ModuleRegistry,
NumberEditorModule,
TextEditorModule,
ValueFormatterParams,
createGrid,
enableDevValidations,
} from "ag-grid-community";
// Enable extended validations only for development
if (process.env.NODE_ENV !== "production") {
enableDevValidations();
}
ModuleRegistry.registerModules([
NumberEditorModule,
TextEditorModule,
DateEditorModule,
ClientSideRowModelApiModule,
ClientSideRowModelModule,
]);
interface IModifiedOlympicData extends IOlympicData {
dateObj: Date | null;
}
let gridApi: GridApi<IModifiedOlympicData>;
const stringToDate = (date: string): Date | null => {
const [day, month, year] = (date || "").split("/");
if (day == null || month == null || year == null) {
return null;
}
return new Date(Number(year), Number(month) - 1, Number(day));
};
const dateToIso = (date: string | null): string => {
const [day, month, year] = (date || "").split("/");
if (day == null || month == null || year == null) {
return "";
}
return `${year}-${month}-${day}`;
};
const gridOptions: GridOptions<IModifiedOlympicData> = {
columnDefs: [
{
field: "athlete",
headerName: "Athlete (maxLength 10)",
cellEditor: "agTextCellEditor",
cellEditorParams: {
maxLength: 10,
},
},
{
field: "age",
headerName: "Age (>= 0 and <= 100)",
cellEditor: "agNumberCellEditor",
cellEditorParams: {
min: 0,
max: 100,
},
},
{
field: "dateObj",
headerName: "Date (< 2009)",
cellEditor: "agDateCellEditor",
valueFormatter: (params: ValueFormatterParams<any, Date>) => {
if (!params.value) {
return "";
}
const month = params.value.getMonth() + 1;
const day = params.value.getDate();
return `${params.value.getFullYear()}-${month < 10 ? "0" + month : month}-${day < 10 ? "0" + day : day}`;
},
cellEditorParams: {
max: new Date("2008-12-31"),
},
},
{
field: "date",
headerName: "Date as String (> 2008)",
cellEditor: "agDateStringCellEditor",
cellEditorParams: {
min: "2008-12-31",
},
},
],
defaultColDef: {
editable: true,
cellDataType: false,
},
};
const gridDiv = document.querySelector<HTMLElement>("#myGrid")!;
gridApi = createGrid(gridDiv, gridOptions);
fetch("https://www.ag-grid.com/example-assets/olympic-winners.json")
.then((response) => response.json())
.then((data: IOlympicData[]) =>
gridApi!.setGridOption(
"rowData",
data.map((rec: IOlympicData) => ({
...rec,
date: dateToIso(rec.date),
dateObj: stringToDate(rec.date),
})),
),
);
<div id="myGrid" style="height: 100%"></div>
Overriding Validation Copy Link
To add custom validation logic to a Provided Editor, use the getValidationErrors() callback inside the ICellEditorParams. This allows you to define additional rules that are specific to your application.
Properties available on the ICellEditorParams<TData = any, TValue = any, TContext = any> interface.
Optional validation callback that will override the getValidationErrors() of Provided Editors. Use this to return your own custom errors.
Returns: An array of strings containing the editor error messages, or null if the editor is valid.
|
const gridOptions = {
columnDefs: [
{
field: 'athlete',
cellEditorParams: {
getValidationErrors: (params) => {
const { value } = params;
if (!value || value.length < 3) {
return ['The value has to be at least 3 characters long.'];
}
return null;
},
},
},
],
// other grid options ...
}If the callback returns errors, the Grid will show the errors in a tooltip when hovering the editor and discard the edit value before completing (depending on the Validation Modes).
This is demonstrated in the following example, note that:
Athletehas to be at least3characters.Agehas to be different than18.
import {
ClientSideRowModelApiModule,
ClientSideRowModelModule,
GridApi,
GridOptions,
IErrorValidationParams,
ModuleRegistry,
NumberEditorModule,
TextEditorModule,
createGrid,
enableDevValidations,
} from "ag-grid-community";
import { IOlympicData } from "./interfaces";
// Enable extended validations only for development
if (process.env.NODE_ENV !== "production") {
enableDevValidations();
}
ModuleRegistry.registerModules([
NumberEditorModule,
TextEditorModule,
ClientSideRowModelApiModule,
ClientSideRowModelModule,
]);
let gridApi: GridApi<IOlympicData>;
const gridOptions: GridOptions<IOlympicData> = {
columnDefs: [
{
field: "athlete",
cellEditorParams: {
getValidationErrors: (params: IErrorValidationParams) => {
const { value } = params;
if (!value || value.length < 3) {
return ["The value has to be at least 3 characters long."];
}
return null;
},
},
},
{
field: "age",
cellEditorParams: {
getValidationErrors: (params: IErrorValidationParams) => {
const { value } = params;
if (value != null && value == 18) {
return ["Value has to be different than 18"];
}
return null;
},
},
},
],
defaultColDef: {
editable: true,
},
};
const gridDiv = document.querySelector<HTMLElement>("#myGrid")!;
gridApi = createGrid(gridDiv, gridOptions);
fetch("https://www.ag-grid.com/example-assets/olympic-winners.json")
.then((response) => response.json())
.then((data: IOlympicData[]) => gridApi!.setGridOption("rowData", data));
<div id="myGrid" style="height: 100%"></div>
export interface IOlympicData {
athlete: string,
age: number,
country: string,
year: number,
date: string,
sport: string,
gold: number,
silver: number,
bronze: number,
total: number
} Validation Modes Copy Link
The Grid supports two modes for handling invalid edits, configured via the grid option invalidEditValueMode:
| Mode | Description |
|---|---|
'revert' (default) | Cancels the edit and reverts the cell to its original value if the value is invalid. |
'block' | Prevents the editor from closing until a valid value is provided. Other editors cannot be started until the current edit is completed or cancelled. |
Use the 'block' mode when you want to strictly enforce valid input before allowing the user to proceed.
const gridOptions = {
invalidEditValueMode: 'block',
// other grid options ...
}import {
ClientSideRowModelApiModule,
ClientSideRowModelModule,
EditValidationCommitType,
GridApi,
GridOptions,
ModuleRegistry,
NumberEditorModule,
TextEditorModule,
createGrid,
enableDevValidations,
} from "ag-grid-community";
import { IOlympicData } from "./interfaces";
// Enable extended validations only for development
if (process.env.NODE_ENV !== "production") {
enableDevValidations();
}
ModuleRegistry.registerModules([
NumberEditorModule,
TextEditorModule,
ClientSideRowModelApiModule,
ClientSideRowModelModule,
]);
let gridApi: GridApi<IOlympicData>;
const gridOptions: GridOptions<IOlympicData> = {
columnDefs: [
{
field: "athlete",
headerName: "Athlete (maxLength 10)",
cellEditor: "agTextCellEditor",
cellEditorParams: {
maxLength: 10,
},
},
{
field: "age",
headerName: "Age (>= 0 and <=100)",
cellEditor: "agNumberCellEditor",
cellEditorParams: {
min: 0,
max: 100,
},
},
],
defaultColDef: {
editable: true,
},
invalidEditValueMode: "revert", // default value
};
const gridDiv = document.querySelector<HTMLElement>("#myGrid")!;
gridApi = createGrid(gridDiv, gridOptions);
fetch("https://www.ag-grid.com/example-assets/olympic-winners.json")
.then((response) => response.json())
.then((data: IOlympicData[]) => gridApi!.setGridOption("rowData", data));
function onValidationModeSelect() {
const value: "revert" | "block" = document.querySelector<HTMLSelectElement>(
"#select-validation-mode",
)?.value as EditValidationCommitType;
gridApi.setGridOption("invalidEditValueMode", value);
}
if (typeof window !== "undefined") {
// Attach external event handlers to window so they can be called from index.html
(<any>window).onValidationModeSelect = onValidationModeSelect;
}
.example-wrapper {
display: flex;
flex-direction: column;
height: 100%;
}
.example-header {
margin-bottom: 10px;
}
#myGrid {
flex: 1 1 0px;
width: 100%;
}
<div class="example-wrapper">
<div class="example-header">
<label>
<span>Cell Editor Validation Mode: </span>
<select id="select-validation-mode" onchange="onValidationModeSelect()">
<option value="revert">revert</option>
<option value="block">block</option>
</select>
</label>
</div>
<div id="myGrid" style="height: 100%"></div>
</div>
export interface IOlympicData {
athlete: string,
age: number,
country: string,
year: number,
date: string,
sport: string,
gold: number,
silver: number,
bronze: number,
total: number
} Full Row Editing Validation Copy Link
When using Full Row Editing, the Grid will validate each cell editor in the row individually, using the same mechanisms described in the previous sections.
In addition, the Grid can also perform cross-field validation by using the optional callback getFullRowEditValidationErrors(params). This allows you to implement logic that checks relationships between fields — for example, ensuring that one field is greater than another.
This callback should return an array of error strings if the row is in an invalid state. If no errors are found, it should return null.
Validates the Full Row Edit. Only relevant when editType="fullRow". |
const gridOptions = {
getFullRowEditValidationErrors: (params) => {
const { data } = params;
if (data.min > data.max) {
return ['Min cannot be greater than Max'];
}
return null;
},
// other grid options ...
}A row edit will only complete successfully if both the individual cell editors and the full-row validation return no errors.
This is demonstrated in the following example. Note the following validation rules:
Weighthas to be a positive value below500.Heighthas to be a positive value below300.- Full Row Edit Validation ensures that the Body Mass Index (BMI), calculated using height and weight, is between
10and80.
import {
ClientSideRowModelModule,
GridApi,
GridOptions,
ModuleRegistry,
NumberEditorModule,
SelectEditorModule,
TextEditorModule,
createGrid,
enableDevValidations,
} from "ag-grid-community";
import {
ColumnMenuModule,
ColumnsToolPanelModule,
ContextMenuModule,
} from "ag-grid-enterprise";
// Enable extended validations only for development
if (process.env.NODE_ENV !== "production") {
enableDevValidations();
}
ModuleRegistry.registerModules([
ClientSideRowModelModule,
ColumnsToolPanelModule,
ColumnMenuModule,
ContextMenuModule,
SelectEditorModule,
TextEditorModule,
NumberEditorModule,
]);
let gridApi: GridApi;
const gridOptions: GridOptions = {
columnDefs: [
{
field: "name",
},
{
field: "weight",
headerName: "Weight (kg)",
cellDataType: "number",
cellEditorParams: {
min: 0,
max: 500,
},
},
{
field: "height",
headerName: "Height (cm)",
cellDataType: "number",
cellEditorParams: {
min: 0,
max: 300,
},
},
{
headerName: "BMI",
cellDataType: "number",
valueGetter: (params) => {
const { weight, height } = params.data ?? {};
if (!weight || !height) return null;
const heightM = height / 100;
return weight / (heightM * heightM);
},
valueFormatter: (params) => params.value?.toFixed(2),
editable: false,
},
],
defaultColDef: {
flex: 1,
editable: true,
cellDataType: false,
},
editType: "fullRow",
rowData: getRowData(),
invalidEditValueMode: "block",
getFullRowEditValidationErrors: ({ editorsState }) => {
const values = Object.fromEntries(
editorsState.map(({ colId, newValue }) => [colId, newValue]),
);
const weight = parseFloat(values["weight"]);
const height = parseFloat(values["height"]);
const heightM = height / 100;
const bmi = weight / (heightM * heightM);
const errors: string[] = [];
if (bmi < 10 || bmi > 80) {
errors.push(
`BMI value of ${bmi.toFixed(2)} is not realistic. Please verify the input.`,
);
}
return errors.length ? errors : null;
},
};
function getRowData() {
const rowData = [
{ name: "Alice", weight: 68, height: 165 },
{ name: "Bob", weight: 85, height: 178 },
{ name: "Charlie", weight: 72, height: 172 },
{ name: "Diana", weight: 54, height: 160 },
{ name: "Ethan", weight: 90, height: 182 },
{ name: "Fiona", weight: 63, height: 168 },
{ name: "George", weight: 77, height: 175 },
{ name: "Hannah", weight: 59, height: 162 },
{ name: "Ian", weight: 95, height: 185 },
{ name: "Julia", weight: 70, height: 170 },
];
return rowData;
}
// wait for the document to be loaded, otherwise
// AG Grid will not find the div in the document.
const eGridDiv = document.querySelector<HTMLElement>("#myGrid")!;
gridApi = createGrid(eGridDiv, gridOptions);
.example-wrapper {
display: flex;
flex-direction: column;
height: 100%;
}
#myGrid {
flex: 1 1 0px;
width: 100%;
}
<div id="myGrid" style="height: 100%"></div>
Validation of Custom Editors Copy Link
Custom Cell Editors can participate in the Grid's validation system by optionally implementing the following methods:
Properties available on the ICellEditor<TValue = any> interface.
Optional: Returns the element to use for validation feedback. Called by the grid in two contexts: tooltip: true → used as the anchor for validation tooltips. tooltip: false → receives the invalid CSS class for visual feedback. tooltip - Whether the element is for a tooltip or direct styling.
Returns: An HTML element for feedback, or null/undefined to use default behavior.
|
Optional: The error messages associated with the Editor
|
These methods are called automatically before the Grid attempts to complete the edit. You can also manually trigger validation by calling the validate() method available in the cellEditorParams, for example:
cellEditorParams.validate();This is useful if you want to validate input during editing, such as in response to an onInput event in the Custom Phone Editor.
import {
ClientSideRowModelModule,
ColDef,
CustomEditorModule,
GridApi,
GridOptions,
ModuleRegistry,
NumberEditorModule,
TextEditorModule,
createGrid,
enableDevValidations,
} from "ag-grid-community";
import { RichSelectModule } from "ag-grid-enterprise";
import { getData } from "./data";
import { PhoneEditor } from "./phoneEditor";
// Enable extended validations only for development
if (process.env.NODE_ENV !== "production") {
enableDevValidations();
}
ModuleRegistry.registerModules([
ClientSideRowModelModule,
RichSelectModule,
NumberEditorModule,
TextEditorModule,
CustomEditorModule,
]);
const columnDefs: ColDef[] = [
{ field: "name" },
{
field: "phone",
headerName: "Custom Phone Editor",
cellEditor: PhoneEditor,
},
];
let gridApi: GridApi;
const gridOptions: GridOptions = {
columnDefs: columnDefs,
rowData: getData(),
defaultColDef: {
editable: true,
flex: 1,
minWidth: 100,
},
};
const gridDiv = document.querySelector<HTMLElement>("#myGrid")!;
gridApi = createGrid(gridDiv, gridOptions);
.phone-cell-editor {
width: 100%;
height: 100%;
box-sizing: border-box;
border: 1px solid transparent;
padding: 0.25rem 0.5rem;
}
.phone-cell-editor:focus {
outline: none;
}
.phone-cell-editor:focus:not(:invalid) {
border-color: blue;
}
export function getData(): any[] {
return [
{ name: 'Alice Johnson', phone: '(415) 555-1234' },
{ name: 'Brian Smith', phone: '(212) 555-9876' },
{ name: 'Catherine Lee', phone: '(310) 555-4567' },
{ name: 'Daniel Kim', phone: '(646) 555-7890' },
{ name: 'Emily Davis', phone: '(408) 555-3210' },
{ name: 'Franklin Moore', phone: '(702) 555-6543' },
{ name: 'Grace Patel', phone: '(503) 555-8888' },
{ name: 'Henry Clark', phone: '(214) 555-4321' },
{ name: 'Isabella Torres', phone: '(617) 555-1122' },
{ name: "James O'Neil", phone: '(303) 555-3344' },
];
}
import type { ICellEditorComp, ICellEditorParams } from 'ag-grid-community';
export class PhoneEditor implements ICellEditorComp<string> {
private eInput!: HTMLInputElement;
private value: string = '';
private params!: ICellEditorParams<any, string>;
private validationError: string | null = null;
public init(params: ICellEditorParams<any, string>): void {
this.params = params;
this.value = params.value || '';
this.eInput = document.createElement('input');
this.eInput.type = 'text';
this.eInput.value = this.value;
this.eInput.placeholder = '(123) 456-7890';
this.eInput.classList.add('phone-cell-editor');
this.eInput.addEventListener('input', () => {
this.validatePhone();
});
this.eInput.addEventListener('blur', () => {
this.validatePhone();
});
}
public getGui(): HTMLElement {
return this.eInput;
}
public afterGuiAttached(): void {
this.eInput.focus();
this.eInput.select();
const { cellStartedEdit, eventKey } = this.params;
if (cellStartedEdit && eventKey?.length === 1) {
this.eInput.value = eventKey;
}
}
public getValue(): string {
return this.eInput.value;
}
public isCancelAfterEnd(): boolean {
return false;
}
// Optional validation helper for AG Grid
public getValidationErrors(): string[] | null {
return this.validationError ? [this.validationError] : null;
}
public getValidationElement(): HTMLElement {
return this.eInput;
}
private validatePhone(): void {
const val = this.eInput.value.trim();
const phoneRegex = /^\(\d{3}\)\s\d{3}-\d{4}$/;
if (!phoneRegex.test(val)) {
this.validationError = 'Invalid phone format. Use (123) 456-7890';
} else {
this.validationError = null;
}
this.params.validate?.();
}
}
<div id="myGrid" style="height: 100%"></div>