Column definitions can be generated automatically from rowData, without defining them upfront. This is useful when working with dynamic or unknown data shapes.
Set autoGenerateColumnDefs to true and the grid scans rowData for the first non-null row, then creates a column for each of its keys:
import { Component } from "@angular/core";
import { AgGridAngular } from "ag-grid-angular";
import {
AutoGenerateColumnDefsOptions,
AutoGenerateColumnsModule,
ClientSideRowModelModule,
GridApi,
GridOptions,
GridReadyEvent,
ModuleRegistry,
enableDevValidations,
} from "ag-grid-community";
if (process.env.NODE_ENV !== "production") {
// Enable extended validations only for development
enableDevValidations();
}
ModuleRegistry.registerModules([
ClientSideRowModelModule,
AutoGenerateColumnsModule,
]);
@Component({
selector: "my-app",
standalone: true,
imports: [AgGridAngular],
template: `<ag-grid-angular
style="width: 100%; height: 100%;"
[autoGenerateColumnDefs]="true"
[rowData]="rowData"
/> `,
})
export class AppComponent {
rowData: any[] | null = [
{
name: "Alice",
address: { city: "London", country: "UK" },
scores: { maths: 92, science: 88 },
},
{
name: "Bob",
address: { city: "Paris", country: "France" },
scores: { maths: 75, science: 91 },
},
{
name: "Charlie",
address: { city: "Berlin", country: "Germany" },
scores: { maths: 84, science: 79 },
},
{
name: "Diana",
address: { city: "Madrid", country: "Spain" },
scores: { maths: 96, science: 85 },
},
{
name: "Eve",
address: { city: "Rome", country: "Italy" },
scores: { maths: 68, science: 94 },
},
];
}
import '@angular/compiler';
import { provideHttpClient } from '@angular/common/http';
import { enableProdMode } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app.component';
if (new URLSearchParams(window.location.search).get('prod') !== 'false') {
enableProdMode();
}
const app = bootstrapApplication(AppComponent, {
providers: [provideHttpClient()],
});
const gridOptions = {
autoGenerateColumnDefs: true,
rowData: myData,
}; Column Generation Copy Link
The grid scans rowData for the first non-null row and creates columns based on value types:
- Primitive values (strings, numbers, booleans, bigints, Date instances) — a leaf column.
- Plain objects — a column group by default, with nested keys recursed into. The
headerNameis derived from the key name. - Arrays — a leaf column if the first element is a primitive value; skipped if the array is empty or contains objects. Arrays render as a comma-separated string.
- null/undefined values — a leaf column by default.
- Functions — skipped.
For example, given this row data:
rowData: [
{ user: { name: 'Alice', age: 30 }, score: 100 },
]The generated column definitions are:
columnDefs: [
{
headerName: 'User',
children: [
{ field: 'user.name', headerName: 'Name' },
{ field: 'user.age', headerName: 'Age' },
],
},
{ field: 'score' },
]Row data keys containing dots (e.g. "user.name") are treated as nested paths by default. Set suppressFieldDotNotation to true to treat dotted keys as literal field names. See Accessing Row Data Values for details.
Column groups are only produced when the value for a key in the first scanned row is a non-empty plain object, with the default objectValues: 'group'. Arrays, primitives, dates, class instances and empty objects all yield leaf columns or are skipped. Ensure rowData has the nested structure you expect before relying on generated group headers.
Configuration Copy Link
Pass an AutoGenerateColumnDefsOptions object instead of true to control how each value type is handled. The example below lets you toggle each option to see how it affects the generated columns:
import { Component } from "@angular/core";
import { AgGridAngular } from "ag-grid-angular";
import "./styles.css";
import {
AutoGenerateColumnDefsOptions,
AutoGenerateColumnsModule,
ClientSideRowModelModule,
GridApi,
GridOptions,
GridReadyEvent,
ModuleRegistry,
enableDevValidations,
} from "ag-grid-community";
if (process.env.NODE_ENV !== "production") {
// Enable extended validations only for development
enableDevValidations();
}
ModuleRegistry.registerModules([
ClientSideRowModelModule,
AutoGenerateColumnsModule,
]);
@Component({
selector: "my-app",
standalone: true,
imports: [AgGridAngular],
template: `<div class="example-wrapper">
<div class="example-header">
<label>
objectValues:
<select id="objectValues" (change)="onObjectValues()">
<option value="group">group</option>
<option value="flatten">flatten</option>
<option value="skip">skip</option>
</select>
</label>
<label>
arrayValues:
<select id="arrayValues" (change)="onArrayValues()">
<option value="primitives">primitives</option>
<option value="include">include</option>
<option value="skip">skip</option>
</select>
</label>
<label>
nullishValues:
<select id="nullishValues" (change)="onNullishValues()">
<option value="include">include</option>
<option value="skip">skip</option>
</select>
</label>
</div>
<ag-grid-angular
style="width: 100%; height: 100%;"
[rowData]="rowData"
[autoGenerateColumnDefs]="autoGenerateColumnDefs"
(gridReady)="onGridReady($event)"
/>
</div> `,
})
export class AppComponent {
private gridApi!: GridApi;
rowData: any[] | null = [
{
name: "Alice",
age: 32,
address: { city: "London", country: "UK" },
roles: ["admin", "user"],
notes: null,
},
{
name: "Bob",
age: 28,
address: { city: "Paris", country: "France" },
roles: ["user"],
notes: null,
},
{
name: "Charlie",
age: 35,
address: { city: "Berlin", country: "Germany" },
roles: ["admin"],
notes: null,
},
{
name: "Diana",
age: 24,
address: { city: "Madrid", country: "Spain" },
roles: ["user", "moderator"],
notes: null,
},
{
name: "Eve",
age: 29,
address: { city: "Rome", country: "Italy" },
roles: ["admin", "moderator"],
notes: null,
},
];
autoGenerateColumnDefs: boolean | AutoGenerateColumnDefsOptions = {
...config,
};
onObjectValues(): void {
const value = (document.getElementById("objectValues") as HTMLSelectElement)
.value;
config.objectValues =
value as AutoGenerateColumnDefsOptions["objectValues"];
this.gridApi.setGridOption("autoGenerateColumnDefs", { ...config });
}
onArrayValues(): void {
const value = (document.getElementById("arrayValues") as HTMLSelectElement)
.value;
config.arrayValues = value as AutoGenerateColumnDefsOptions["arrayValues"];
this.gridApi.setGridOption("autoGenerateColumnDefs", { ...config });
}
onNullishValues(): void {
const value = (
document.getElementById("nullishValues") as HTMLSelectElement
).value;
config.nullishValues =
value as AutoGenerateColumnDefsOptions["nullishValues"];
this.gridApi.setGridOption("autoGenerateColumnDefs", { ...config });
}
onGridReady(params: GridReadyEvent) {
this.gridApi = params.api;
}
}
const config: AutoGenerateColumnDefsOptions = {
objectValues: "group",
arrayValues: "primitives",
nullishValues: "include",
};
.example-wrapper {
display: flex;
flex-direction: column;
height: 100%;
}
#myGrid {
flex: 1 1 0px;
width: 100%;
}
.example-header {
font-family: Verdana, Geneva, Tahoma, sans-serif;
font-size: 13px;
margin-bottom: 5px;
display: flex;
gap: 16px;
align-items: center;
}
import '@angular/compiler';
import { provideHttpClient } from '@angular/common/http';
import { enableProdMode } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app.component';
if (new URLSearchParams(window.location.search).get('prod') !== 'false') {
enableProdMode();
}
const app = bootstrapApplication(AppComponent, {
providers: [provideHttpClient()],
});
Properties available on the AutoGenerateColumnDefsOptions interface.
How to handle plain-object values. 'group' recurses into the object and creates a column group, 'flatten' recurses and creates flat leaf columns using dotted field paths, 'skip' ignores the field entirely. |
How to handle array values. 'primitives' creates a leaf column only when the first element is a primitive value, 'include' creates a leaf column for all arrays, 'skip' ignores them. |
How to handle null and undefined values. 'include' creates a leaf column, 'skip' ignores them. |
Setting objectValues to 'flatten' produces top-level columns with dotted field paths instead of nested groups:
// Row data: { name: 'Alice', address: { city: 'London' } }
// With objectValues: 'flatten':
columnDefs: [
{ field: 'name' },
{ field: 'address.city', headerName: 'City' },
] Updating Row Data Copy Link
Columns are regenerated each time rowData is set. Only the keys from the first non-null row are used, so columns update when the data shape changes.
By default, column order matches the key order of that first row. Set maintainColumnOrder to true to preserve existing column positions when the data shape changes:
const gridOptions = {
autoGenerateColumnDefs: true,
maintainColumnOrder: true,
};See Maintain Column Order for details.
Setting rowData to [] clears both the rows and the column definitions.
Updating data via transactions (applyTransaction / applyTransactionAsync) does not trigger column generation.
Customising Generated Columns Copy Link
Default Column Definitions apply to auto-generated columns the same way as manually defined ones. This is the simplest way to set common properties like filtering or resizing.
For per-column customisation, use the processAutoGeneratedColumnDefs callback to modify, reorder, or replace columns before they are applied. The callback receives params.columnDefs (which may include ColGroupDef entries when row data contains nested objects) and params.rowData, and returns the final (ColDef | ColGroupDef)[].
Use forEachColDef to mutate leaf column properties without having to handle group recursion yourself:
import { forEachColDef } from 'ag-grid-community';
const gridOptions = {
autoGenerateColumnDefs: true,
rowData: myData,
processAutoGeneratedColumnDefs: ({ columnDefs }) => {
forEachColDef(columnDefs, (colDef) => {
colDef.hide = colDef.field === 'internalId';
});
// Add a custom column
columnDefs.push({ headerName: 'Actions', cellRenderer: ActionsRenderer });
return columnDefs;
},
};The example below uses Column Types to apply a currencyColumn type to any column whose field contains "profit":
import { Component } from "@angular/core";
import { AgGridAngular } from "ag-grid-angular";
import {
AutoGenerateColumnDefsOptions,
AutoGenerateColumnsModule,
ClientSideRowModelModule,
ColDef,
ColTypeDefs,
GridApi,
GridOptions,
GridReadyEvent,
ModuleRegistry,
NumberFilterModule,
ProcessAutoGeneratedColumnDefs,
enableDevValidations,
forEachColDef,
} from "ag-grid-community";
if (process.env.NODE_ENV !== "production") {
// Enable extended validations only for development
enableDevValidations();
}
ModuleRegistry.registerModules([
ClientSideRowModelModule,
AutoGenerateColumnsModule,
NumberFilterModule,
]);
@Component({
selector: "my-app",
standalone: true,
imports: [AgGridAngular],
template: `<ag-grid-angular
style="width: 100%; height: 100%;"
[processAutoGeneratedColumnDefs]="processAutoGeneratedColumnDefs"
[autoGenerateColumnDefs]="true"
[columnTypes]="columnTypes"
[defaultColDef]="defaultColDef"
[rowData]="rowData"
/> `,
})
export class AppComponent {
processAutoGeneratedColumnDefs: ProcessAutoGeneratedColumnDefs = (params) => {
forEachColDef(params.columnDefs, (colDef) => {
if (colDef.field?.includes("profit")) {
colDef.type = "currencyColumn";
}
});
return params.columnDefs;
};
columnTypes: ColTypeDefs = {
currencyColumn: {
valueFormatter: (params) =>
params.value == null ? "" : "£" + params.value.toLocaleString(),
filter: "agNumberColumnFilter",
},
};
defaultColDef: ColDef = {
flex: 1,
};
rowData: any[] | null = [
{ product: "Widget A", region: "North", profit: 12500, profitMargin: 0.15 },
{ product: "Widget B", region: "South", profit: 8300, profitMargin: 0.11 },
{ product: "Gadget X", region: "East", profit: 21000, profitMargin: 0.22 },
{ product: "Gadget Y", region: "West", profit: 5600, profitMargin: 0.08 },
{ product: "Gizmo Z", region: "North", profit: 17400, profitMargin: 0.19 },
];
}
import '@angular/compiler';
import { provideHttpClient } from '@angular/common/http';
import { enableProdMode } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app.component';
if (new URLSearchParams(window.location.search).get('prod') !== 'false') {
enableProdMode();
}
const app = bootstrapApplication(AppComponent, {
providers: [provideHttpClient()],
});
File Drop Overlay Copy Link
When autoGenerateColumnDefs is enabled and no rowData is present, a file drop overlay is shown automatically when processFileInput is provided. Files can be dragged onto the overlay or selected via a browse button. See Overlays for more on grid overlays.
Provide a processFileInput callback. The params object contains the selected files array along with success and fail callbacks. Call success(rowData) to load the parsed data into the grid, or fail(message) to display an error in the overlay using the fileInputProcessingFailed locale.
params.files contains every file the user dropped or selected. The example below processes the first file only; iterate over params.files to handle multiple files.
const gridOptions = {
autoGenerateColumnDefs: true,
processFileInput: (params) => {
const file = params.files[0];
const reader = new FileReader();
reader.onload = (e) => {
try {
const rowData = parseCsv(e.target.result);
params.success(rowData);
} catch {
params.fail('Failed to parse file');
}
};
reader.readAsText(file);
},
};The example below auto-generates columns from various data sources. Files can be dragged onto the overlay, selected via the browse button, or loaded from the sample data dropdown. The toolbar Upload File button re-shows the overlay by setting activeOverlay to 'agFileInputOverlay'.
import { Component } from "@angular/core";
import { AgGridAngular } from "ag-grid-angular";
import "./styles.css";
import {
AutoGenerateColumnDefsOptions,
AutoGenerateColumnsModule,
ClientSideRowModelModule,
ColDef,
GridApi,
GridOptions,
GridReadyEvent,
ModuleRegistry,
NumberFilterModule,
ProcessFileInputParams,
TextFilterModule,
Toolbar,
ToolbarItemActionParams,
enableDevValidations,
} from "ag-grid-community";
import { ToolbarModule } from "ag-grid-enterprise";
if (process.env.NODE_ENV !== "production") {
// Enable extended validations only for development
enableDevValidations();
}
ModuleRegistry.registerModules([
ClientSideRowModelModule,
AutoGenerateColumnsModule,
TextFilterModule,
NumberFilterModule,
ToolbarModule,
]);
declare let XLSX: any;
@Component({
selector: "my-app",
standalone: true,
imports: [AgGridAngular],
template: `<div class="example-wrapper">
<div class="example-header">
<label>
Sample data:
<select id="sampleData" (change)="onLoadSampleData()">
<option value="">-- Select --</option>
<option value="small-row-data.json">Cars (JSON)</option>
<option value="small-olympic-winners.json">
Olympic Winners (JSON)
</option>
<option value="weather-se-england.json">Weather (JSON)</option>
<option value="stocks.json">Stocks (JSON)</option>
<option value="olympic-data.xlsx">Olympic Data (Excel)</option>
</select>
</label>
</div>
<ag-grid-angular
style="width: 100%; height: 100%;"
[processFileInput]="processFileInput"
[autoGenerateColumnDefs]="true"
[defaultColDef]="defaultColDef"
[toolbar]="toolbar"
[rowData]="rowData"
(gridReady)="onGridReady($event)"
/>
</div> `,
})
export class AppComponent {
private gridApi!: GridApi;
defaultColDef: ColDef = {
minWidth: 80,
flex: 1,
};
toolbar: Toolbar = {
items: [
{
label: "Upload File",
icon: "document",
alignment: "right",
action: (params: ToolbarItemActionParams) => {
const curr = params.api.getGridOption("activeOverlay");
params.api.setGridOption(
"activeOverlay",
curr === "agFileInputOverlay" ? undefined : "agFileInputOverlay",
);
},
},
],
};
rowData!: any[];
onLoadSampleData(): void {
const select = document.getElementById("sampleData") as HTMLSelectElement;
const value = select.value;
if (!value) return;
if (value.endsWith(".xlsx")) {
fetch(`https://www.ag-grid.com/example-assets/${value}`)
.then((response) => response.arrayBuffer())
.then((data: ArrayBuffer) => {
const workbook = XLSX.read(new Uint8Array(data));
this.gridApi.updateGridOptions({
activeOverlay: undefined,
rowData: parseWorkbook(workbook),
});
});
} else {
fetch(`https://www.ag-grid.com/example-assets/${value}`)
.then((response) => response.json())
.then((rows) => {
this.gridApi.updateGridOptions({
activeOverlay: undefined,
rowData: rows,
});
});
}
}
onGridReady(params: GridReadyEvent) {
this.gridApi = params.api;
}
processFileInput = (params: ProcessFileInputParams): void => {
const file = params.files[0];
if (!file) return;
const reader = new FileReader();
reader.onerror = () => {
params.fail("Failed to read file");
};
reader.onload = (e) => {
try {
const workbook = XLSX.read(
new Uint8Array(e.target?.result as ArrayBuffer),
);
params.success(parseWorkbook(workbook));
} catch (error) {
console.error(error);
params.fail("Failed to parse file");
}
};
reader.readAsArrayBuffer(file);
};
}
function parseWorkbook(workbook: any): Record<string, unknown>[] {
const firstSheetName = workbook.SheetNames[0];
const worksheet = workbook.Sheets[firstSheetName];
return XLSX.utils.sheet_to_json(worksheet);
}
.example-wrapper {
display: flex;
flex-direction: column;
height: 100%;
}
#myGrid {
flex: 1 1 0px;
width: 100%;
}
.example-header {
font-family: Verdana, Geneva, Tahoma, sans-serif;
font-size: 13px;
margin-bottom: 5px;
display: flex;
gap: 16px;
align-items: center;
}
import '@angular/compiler';
import { provideHttpClient } from '@angular/common/http';
import { enableProdMode } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app.component';
if (new URLSearchParams(window.location.search).get('prod') !== 'false') {
enableProdMode();
}
const app = bootstrapApplication(AppComponent, {
providers: [provideHttpClient()],
});