developer guide
AG Grid Migration Guide: Options, Events and API
A name-for-name map from an AG Grid configuration to a Lattice Grid one, a real forty-line setup converted, the warnings the grid prints while you convert, and an honest list of what works differently and what does not come across at all.
Before you convert
Five minutes of preparation saves most of the surprises. Work through this before you rename anything.
-
Run ESLint's
no-dupe-keysrule over your options objects. A key written twice in one object literal is legal JavaScript: the last one wins and the first is discarded before any grid is handed it. One real options object carried twogetRowStyledefinitions, and the shading rule in the first had never run once. No grid can warn about something it was never given, so this is the one check worth doing while the old configuration is still the live one. - Write down the options you actually use. Most configurations use a few dozen of the several hundred available. The table below is a name-for-name map for the ones that come up; anything missing from it usually has a name in the API reference, under the part of the grid it belongs to.
- Decide where rows come from. Everything in the browser, a page at a time from an endpoint, blocks fetched as the reader scrolls with the query pushed to the server, or a live feed. The choice carries over one for one, and it is the decision that shapes everything else. See connecting your data.
- Keep the console open on the first run. Every key, type, operator, column id and row key the grid does not recognise is named as it happens, so the work left to do is printed rather than hunted for.
How the pieces map
The shapes are close enough that most of a conversion is renaming, and the differences are worth knowing before you start.
Columns and defaults
columnDefs becomes columns and
defaultColDef becomes columnDefaults, merged under every column
before its own definition. Inside a column, the settings are grouped by what they affect:
value for computing, formatting and parsing, cell for how it is
drawn, edit, sort, filter, layout for
width, pinning and visibility, and header for the heading. So a column that set
eight flat properties sets the same eight, gathered into the part of the column each one is
about.
A house configuration goes one level above that.
defaults({ theme: 'navy', rowHeight: 32 }) is merged beneath the
configuration of every grid built afterwards, so the look and the behaviour you want across
an application are set once, in one place, rather than copied into every screen and drifting.
The per-grid value always wins, so any grid can still say something different for itself.
defaults() reads what is currently in force and
defaults(null) clears it.
Rows and identity
rowData becomes rows and
getRowId becomes rowKey, which takes a field name, a dot path, an
array of fields for a composite key, or a function. Set it. Change tracking, streaming
dedupe, selection that survives a reload and remote reloading all depend on the grid knowing
which row is which.
Sizing columns
columns.fit() sizes the visible resizable columns so that together they exactly
fill the viewport, and columns.autoSize() fits each column to what it is
showing. Both have a declarative form: flex shares out spare width and is
resolved first, and fit: 'content' keeps a column sized to its content as rows
arrive, columns are shown, hidden, reordered or pinned, and the grid is resized.
Selection
One selection block holds the mode and the rest: checkbox for a
generated column of row checkboxes, headerCheckbox for the select-all in its
heading, checkboxOnly so a row whose click opens a record does not also select
it, groupSelectsChildren, ranges and fillHandle. The
checkbox column is generated rather than declared, so it is never exported and never appears
in the tool panel.
Sorting, filtering and the external filter
Filters are one condition tree with a named operator per condition, whatever set them: a
header popup, the filter row, the tool panel or your own code. Reading
filters.get() always gives the whole truth. The external filter becomes a named
predicate: filters.where(name, fn) registers it, and registering it is what
makes it active, so there is no second flag to keep in step. Name the columns it reads in
deps and the grid re-runs it only when one of those changes; call
filters.reapply(name) when something it reads outside the grid changes, such as
a rate table that has just arrived. Several predicates hold at once under their own names,
and one marked pinned survives a clear-filters button, which is what a row-level
permission wants.
Where rows come from
Everything resident in the browser is the default and needs no configuration. A server that
paginates is source: { mode: 'paged', fetch }. A server that should do
the work is mode: 'remote', which sends one published request carrying the
range, sort, filters, quick text, grouping, pivot and totals, so your endpoint targets a
documented shape. A pushdown adapter goes one step further and turns that request into what
an engine already speaks, declaring what it can answer so the grid finishes only what is
left over and tells you which part that was.
Events and saved views
There are no onX configuration properties. Every event goes through one bus,
grid.on('cell:changed', handler), and on() returns its own
unsubscribe. Every user-initiated change is gated by a cancellable before-event that can hold
the action while a confirm dialog or a server check settles. A whole view, columns, order,
filters, quick text, sort, grouping, pivot, expansion, selection, scroll and paging, is one
serialisable object from state.get(); state.apply() restores it and
reports anything it could not apply rather than failing quietly.
One configuration, converted
A support desk grid: seven columns, a computed cost, an editable priority, checkbox selection that does not fight the row click, paging, undo, a row style for a breached ticket, and an external filter behind a "my tickets" toggle. First as it was.
const rates = { low: 65, normal: 85, high: 120 };
const gridOptions = {
columnDefs: [
{ field: 'id', headerName: 'Ticket', pinned: 'left', width: 110 },
{ field: 'customer', headerName: 'Customer', flex: 1, minWidth: 160,
tooltip: (p) => p.data.customer + ' in ' + p.data.region },
{ field: 'region', headerName: 'Region', width: 120 },
{ field: 'opened', headerName: 'Opened', width: 130, filter: 'agDateColumnFilter' },
{ field: 'priority', headerName: 'Priority', width: 120, editable: true,
cellEditor: 'agSelectCellEditor',
cellEditorParams: { values: ['low', 'normal', 'high'] },
cellClassRules: { 'is-high': (p) => p.value === 'high' } },
{ field: 'hours', headerName: 'Hours', width: 110, type: 'numericColumn',
valueFormatter: (p) => (p.value == null ? '' : p.value.toFixed(1)) },
{ colId: 'cost', headerName: 'Cost', width: 130, type: 'numericColumn',
valueGetter: (p) => p.data.hours * rates[p.data.priority],
valueFormatter: (p) => gbp.format(p.value) },
],
defaultColDef: { sortable: true, filter: true, resizable: true },
rowData: tickets,
getRowId: (p) => p.data.id,
rowSelection: { mode: 'multiRow', checkboxes: true, headerCheckbox: true,
enableClickSelection: false },
pagination: true,
paginationPageSize: 25,
paginationPageSizeSelector: [25, 50, 100],
undoRedoCellEditing: true,
undoRedoCellEditingLimit: 20,
getRowStyle: (p) => (p.data.breached ? { background: '#fff4f4' } : undefined),
isExternalFilterPresent: () => onlyMine,
doesExternalFilterPass: (node) => node.data.owner === me,
onGridReady: (e) => e.api.sizeColumnsToFit(),
onCellValueChanged: (e) => save(e.data),
onSelectionChanged: (e) => updateToolbar(e.api.getSelectedRows()),
};
const api = agGrid.createGrid(document.getElementById('grid'), gridOptions);
// Elsewhere: the toggle that drives the external filter.
function toggleMine(on) {
onlyMine = on;
api.onFilterChanged();
}
And converted. The column settings gather into their sub-specs, the computed column declares what it reads so the grid knows when to work it out again, the external filter becomes a named predicate, and the handlers move onto the one bus.
<div id="grid" style="height: 420px"></div>
<script>
const me = 'ana';
const rates = { low: 65, normal: 85, high: 120 };
const tickets = [
{ id: 'T-1001', customer: 'Northwind', region: 'EMEA', opened: '2026-09-02', priority: 'high', hours: 6.5, owner: 'ana', breached: true },
{ id: 'T-1002', customer: 'Contoso', region: 'EMEA', opened: '2026-09-03', priority: 'normal', hours: 2, owner: 'bo', breached: false },
{ id: 'T-1003', customer: 'Fabrikam', region: 'AMER', opened: '2026-09-05', priority: 'low', hours: 1.5, owner: 'ana', breached: false },
{ id: 'T-1004', customer: 'Tailspin', region: 'APAC', opened: '2026-09-07', priority: 'high', hours: 9, owner: 'cai', breached: true },
];
const grid = LatticeGrid.createGrid(document.getElementById('grid'), {
rowKey: 'id',
columnDefaults: { sort: true, filter: true, layout: { resizable: true } },
columns: [
{ field: 'id', title: 'Ticket', layout: { pin: 'start', width: 110 } },
{ field: 'customer', title: 'Customer', layout: { flex: 1, min: 160 },
cell: { tooltip: (p) => p.data.customer + ' in ' + p.data.region } },
{ field: 'region', title: 'Region', layout: { width: 120 } },
{ field: 'opened', title: 'Opened', type: 'date', layout: { width: 130 } },
{ field: 'priority', title: 'Priority', layout: { width: 120 },
lookup: { options: ['low', 'normal', 'high'] },
edit: { enabled: true, editor: 'select' },
cell: { classWhen: { 'is-high': (p) => p.value === 'high' } } },
{ field: 'hours', title: 'Hours', type: 'number', format: { decimals: 1 },
layout: { width: 110 } },
{ id: 'cost', title: 'Cost', type: 'number', layout: { width: 130 },
format: { style: 'currency', currency: 'GBP' },
value: { deps: ['hours', 'priority'], compute: (d) => d.hours * rates[d.priority] } },
],
rows: tickets,
selection: { mode: 'multiple', checkbox: true, headerCheckbox: true, checkboxOnly: true },
pagination: { pageSize: 25, pageSizes: [25, 50, 100] },
edit: { enabled: true, undoDepth: 20 },
rowStyle: (p) => (p.data.breached ? { background: '#fff4f4' } : null),
});
// The external filter becomes a named predicate. Registering it is what makes
// it present, and naming the columns it reads is what lets the grid re-run it
// only when one of them changes on a row.
grid.filters.where('myItems', (row) => row.owner === me, { deps: ['owner'] });
// The toggle that used to call onFilterChanged() removes the predicate by name.
function toggleMine(on) {
grid.filters.where('myItems', on ? (row) => row.owner === me : null, { deps: ['owner'] });
}
grid.on('cell:changed', (e) => save(e.row.data));
grid.on('selection:changed', () => updateToolbar(grid.selection.rows()));
// sizeColumnsToFit() on first data, as one call once the grid is ready.
grid.once('ready', () => grid.columns.fit());
</script>
Two things are worth pointing at. The cost column names hours and
priority in deps, which is what lets the grid recompute that cell
when either changes on a row and leave it alone otherwise. And the "my tickets" toggle no
longer tells the grid that a filter changed: removing the predicate by name is the whole
operation, because there was never a separate flag saying one was present.
The equivalence table
189 names, one row each: 154 that carry straight across, 26 that need a decision, 5 answered another way on purpose, and 4 with no equivalent. AG Grid names follow v36; where AG Grid renamed an option the older name is listed underneath, so an application on either version finds its row. Every Lattice Grid name here is one the shipped API reference documents.
The same table is machine-readable at /docs/migrate/from-ag-grid.json, if you would rather drive a codemod with it than read it.
Grid options
What you set on the options object. 79 rows.
| AG Grid | Lattice Grid | How close | What changes |
|---|---|---|---|
columnDefs
|
columns
|
direct | The column list, in display order. Column groups nest inside it, or are declared on their own with columnGroups. |
rowData
|
rows
|
direct | The row objects. The array is copied on ingest, so the one you pass is never written to. |
defaultColDef
|
columnDefaults
|
direct | Merged under every column before its own definition. |
columnTypes
|
columnPresets
|
partial | Named bundles a column opts into with preset. Lattice keeps type for the data type (money, date, duration), so a named bundle and a data type are separate ideas. |
dataTypeDefinitions
|
dataTypes
|
partial | Types of your own, registered ahead of the built-ins so a name of yours overrides one of ours. A Lattice type carries format, parse, compare, storage, editor, filter and Excel behaviour together. |
getRowId
|
rowKey
|
direct | A field name, a dot path, an array of fields for a composite key, or a function. Set it: change tracking, streaming dedupe and selection persistence all depend on stable identity. |
components
|
components
|
direct | Renderers, editors and filters addressable by name. |
context
|
context
|
direct | Passed to every callback, so formatters and renderers need no closures over application state. |
initialState
|
state
|
direct | A saved view restored at construction. |
localeText
|
messages
|
direct | Replaces the labels, menus and screen-reader announcements. Twenty catalogues ship in the package; a partial object of your own lays over British English. |
enableRtl
|
direction
|
direct | direction: 'rtl' lays the grid out right to left, and an Arabic, Hebrew or Farsi locale does it without setting anything else. |
tooltipShowDelay
|
tooltip.delay
|
direct | How long the pointer or the keyboard cursor rests on a cell before a tooltip is built. tooltip.maxWidth caps how wide it grows. |
rowClass
|
rowClass
|
direct | A class for every row, re-evaluated on each repaint. |
getRowClass
|
rowClass
|
direct | The function form of the same key. |
rowStyle
|
rowStyle
|
direct | Inline styles for every row. |
getRowStyle
|
rowStyle
|
direct | The function form of the same key. Declare it once: two definitions of one key in an object literal means the first is thrown away before the grid ever sees it. |
singleClickEdit
|
edit.start
|
direct | edit.start takes 'single', 'double' or 'key'. |
editType
|
edit.mode
|
direct | edit.mode: 'row' edits the whole row at once; 'cell' is the default. |
enterNavigatesVerticallyAfterEdit
|
edit.enterMovesDown
|
direct | Enter commits and moves down the column. |
undoRedoCellEditing
|
history
|
partial | Undo is always present and covers more than edits: sorts, filters, column moves, grouping, an applied view and a restore each record one labelled entry. |
undoRedoCellEditingLimit
|
edit.undoDepth
|
direct | How many steps are kept. |
stopEditingWhenCellsLoseFocus
|
edit.stop
|
partial | edit.stop(cancel) commits or discards an open editor from your own code, and edit.start opens one, so closing an edit is something you can drive rather than only configure. |
rowSelection
was rowSelection: 'single' | 'multiple'
|
selection
|
direct | The object form takes mode plus checkbox, headerCheckbox, checkboxOnly, groupSelectsChildren, ranges and fillHandle. A bare 'single', 'multiple' or 'none' is accepted as shorthand. |
rowSelection.enableClickSelection
was suppressRowClickSelection
|
selection.checkboxOnly
|
direct | Set checkboxOnly and only the checkbox column changes selection, so a row whose click opens a record no longer selects it as well. |
rowSelection.groupSelects: 'descendants'
was groupSelectsChildren
|
selection.groupSelectsChildren
|
direct | Selecting a group selects the rows beneath it. |
rowSelection.groupSelects: 'filteredDescendants'
was groupSelectsFiltered
|
selection.groupSelectsFiltered
|
direct | Extend that cascade to children the filters exclude. |
cellSelection
was enableRangeSelection
|
selection.ranges
|
direct | Rectangular cell ranges by drag and by Shift with an arrow key. On unless selection is none, and an explicit true brings it back. |
cellSelection.handle
was enableFillHandle
|
selection.fillHandle
|
direct | The drag handle at the corner of a range, and the fill it performs. |
defaultColDef.sortingOrder
was sortingOrder
|
no equivalent | none | The cycle a header click steps through is the grid's. Set the order you want through sort.set() from a control of your own. |
quickFilterText
|
quickFilterText
|
direct | The opening quick-filter term; filters.quick(text) sets it afterwards, in contains, words, fuzzy or regex mode. |
isExternalFilterPresent
|
filters.where
|
direct | Registering a named predicate is what makes it present, so there is no second flag to keep in step. |
doesExternalFilterPass
|
filters.where
|
direct | The predicate you pass to filters.where(name, fn) receives the row. Several are in force at once under their own names. |
rowGroupPanelShow
|
groupPanel
|
direct | A drag-and-drop strip above the headings, with removable and reorderable chips, operable from the keyboard. |
groupDefaultExpanded
|
groupDefaultExpanded
|
direct | True opens every group, a number opens the first levels, a predicate answers per group. |
groupRowRenderer
|
groupRenderer
|
direct | Draw the group row yourself, as one band across the columns. |
groupTotalRow
was groupIncludeFooter
|
groupFooter
|
direct | A closing total row per group. |
grandTotalRow
|
grandTotalRow
|
direct | Inline at the end, or pinned above the status bar. |
suppressAggFuncInHeader
|
showTotalInHeader
|
direct | Set showTotalInHeader: false to keep the reduction out of the heading. |
aggFuncs
|
totalFns
|
direct | Reductions of your own, addressable by name from a column. |
pivotMode
|
pivot
|
direct | The object form carries the totals group, its label and a column ceiling that reports rather than locking the browser. |
treeData
|
tree
|
direct | tree: { path } for a path per row, or { parentKey } for a parent reference. |
getDataPath
|
tree
|
direct | The path key names the field holding each row's path. |
masterDetail
|
detail
|
direct | A master row expands into a nested grid, inline or into an element you supply. |
detailCellRendererParams
|
detail
|
direct | The detail block carries rows, config, render, isMaster, height and where to put it. |
isFullWidthRow
|
fullWidth
|
direct | fullWidth: { when, render } picks the rows and fills them. |
fullWidthCellRenderer
|
fullWidth
|
direct | The render half of the same option. |
autoGroupColumnDef
|
groupRenderer
|
partial | There is no separate generated group column to configure. Draw the group row with groupRenderer, and the sticky group headings and the group panel are configured on the grid. |
pinnedTopRowData
|
pinnedTopRows
|
direct | Held above the scrolling body, and outside the data: not counted, sorted, filtered or exported. |
pinnedBottomRowData
|
pinnedBottomRows
|
direct | The same below the body, under the grand total when both are shown. |
rowNumbers
|
rowNumbers
|
direct | A row-number column the grid generates, numbering the rows as they are displayed, after sort, filter and grouping. Read-only, and out of an export unless you ask for it. |
pagination
|
pagination
|
direct | A window over the rows the query already produced, so a page change costs a repaint and nothing more. |
paginationPageSize
|
pagination.set
|
direct | pagination.set({ pageSize }) moves it at run time; pageSize: 0 shows everything. |
rowBuffer
|
overscan
|
direct | How many rows are drawn beyond the viewport. |
suppressColumnVirtualisation
|
columnVirtualisationAbove
|
partial | Columns virtualise above a column count you set rather than being switched off outright. |
rowDragManaged
|
rowReorder
|
partial | Dragging a handle or Alt and Shift with an arrow key reorders rows. It is refused, with the reason announced, while a sort, filter or grouping is in force, because the order you would be dragging into is not the order stored. |
alignedGrids
|
alignedGrids
|
direct | Widths, order, visibility, pinning and horizontal scroll are shared; sort, filters, selection and rows stay independent. |
rowModelType: 'clientSide'
|
source
|
direct | The default. Every row is present and the grid filters, sorts, groups and totals it. |
rowModelType: 'infinite'
|
source
|
partial | source: { mode: 'paged', fetch } fetches a block at a time over a flat list, with sort and filter handed to the server. |
rowModelType: 'serverSide'
|
source
|
partial | source: { mode: 'remote', fetch } asks the server for a window and pushes sort, filter, grouping, pivot and totals to it. The request is a published shape, so a server targets it rather than guessing. |
serverSideDatasource
|
fetch
|
partial | One fetch function answers one block and returns the rows and, when it knows it, the total. |
cacheBlockSize
|
pageSize
|
direct | How many rows come back per block. |
maxBlocksInCache
|
maxCachedPages
|
direct | How many blocks are kept before the least recently used ones are dropped. |
rowModelType: 'viewport'
|
source
|
by design | A feed that pushes rows is a stream: source: { mode: 'stream', connect } renders as rows land, with a sliding window by count or by age so an overnight feed cannot grow without bound. |
sideBar
|
toolPanel
|
partial | One side dock with the panels you name: columns, filters, views, quick, formatting, statistics, regression. |
statusBar
|
statusBar
|
direct | Composable panels along the bottom, each silent when it has nothing to report. |
getContextMenuItems
|
contextMenu
|
direct | The function form receives the defaults and returns the items you want. contextMenu: false suppresses it, which is what a read-only grid wants. |
getMainMenuItems
|
columnMenu
|
direct | The same shape for the heading menu. |
suppressContextMenu
|
contextMenu
|
direct | contextMenu: false, and a column may suppress its own. |
suppressMenuHide
|
headerControls
|
direct | AG Grid deprecated this in favour of suppressHeaderMenuButton on the column. headerControls: 'always' keeps the sort, filter and menu controls visible instead of revealing them on hover. |
overlayLoadingTemplate
|
overlay
|
partial | grid.overlay puts your own content over the grid, so a loading or empty state is markup you control rather than a template string. |
overlayNoRowsTemplate
|
overlay
|
partial | The same surface covers the empty state. |
domLayout: 'autoHeight'
|
no equivalent | none | The grid fills the element it is mounted in, so size that element. autoHeight in Lattice sizes rows to their content, which is a different thing. |
rowHeight
|
rowHeight
|
direct | A number, or a function for variable-height rows. density sets every geometry token at once if you would rather not pick pixels. |
getRowHeight
|
rowHeight
|
direct | The function form of the same key. |
headerHeight
|
headerHeight
|
direct | Per header row. |
suppressCellFocus
|
no equivalent | by design | The focused cell stays visible: keyboard operation and what a screen reader announces both depend on it. For a plainer read-only look, showColumnFunctions: false leaves each heading as its label. |
theme
was ag-theme-quartz and the other theme classes
|
theme
|
partial | Four themes ship, light, dark, high-contrast and terminal, and everything else is CSS custom properties on the grid root. AG Grid's theming API is the deeper one. |
processCellForClipboard
|
export.clipboard
|
partial | Copy writes tab-separated text; the grid's own paste parser is its counterpart, and a bulk paste can show a confirm-and-cancel preview first. |
excelStyles
|
column.export
|
by design | A column declares what its exported value is; a data type declares how it reaches a spreadsheet, so a time or a duration stays numeric and still sorts in the sheet. Sheet styling is AG Grid's deeper surface. |
Column definitions
What you set on a column. 50 rows.
| AG Grid | Lattice Grid | How close | What changes |
|---|---|---|---|
field
|
field
|
direct | Dot paths are read, so 'site.address.postcode' is a column. |
colId
|
id
|
direct | Defaults to field, and is required on a column that has none. |
headerName
|
title
|
direct | Defaults to a humanised field name. |
children
|
columnGroups
|
direct | Banded headings, declared inside columns or separately with columnGroups. Groups nest. |
width
|
layout.width
|
direct | A pixel number, or a percentage string that follows the container as it resizes. |
minWidth
|
layout.min
|
direct | The floor a resize or a flex share is clamped to. |
maxWidth
|
layout.max
|
direct | The ceiling a resize or a flex share is clamped to. |
flex
|
layout.flex
|
direct | Share out the spare width. Resolved before any other sizing and wins over it. |
hide
|
layout.hidden
|
direct | Start the column hidden; the tool panel and columns.show() bring it back. |
pinned
|
layout.pin
|
direct | 'start' or 'end' rather than left or right, so a pinned column holds the leading edge in a right-to-left grid too. |
resizable
|
layout.resizable
|
direct | Whether the user may drag this column wider or narrower. |
suppressMovable
|
layout.movable
|
direct | Set movable: false for the same effect. |
lockPosition
|
layout.lockPosition
|
direct | Hold the column where it is while others move around it. |
lockVisible
|
layout.lockVisible
|
direct | Keep the column on screen whatever the tool panel offers. |
rowSpan
|
rowSpan
|
direct | A function of the cell returning how many rows to merge down. Spanned cells draw in their own layer, so scrolling cannot clip them. |
colSpan
|
colSpan
|
direct | A function of the cell returning how many columns to merge across. |
tooltip
was tooltipField, tooltipValueGetter
|
cell.tooltip
|
direct | A string or a function is plain text and becomes the browser title, the same as it was. |
tooltipComponent
|
cell.tooltip
|
direct | The object form is a tooltip the grid draws: render returns an element, a { title, rows, note } spec, an { html } wrapper or a string, and mount and unmount carry live content. Shown on keyboard focus as well as hover, and dismissed with Escape. |
valueGetter
|
value.compute
|
partial | A computed value declares the columns it reads in value.deps, so the grid knows when to recompute it and can catch a cycle before it renders. |
valueFormatter
|
value.format
|
direct | Overrides the data type's formatter for that column. A format string such as 'percent:1' covers the common cases without a function. |
valueParser
|
value.parse
|
direct | Turns what the editor produced back into a value. Always called, whatever the editor emitted. |
valueSetter
|
value.apply
|
direct | Writes the value back into the row object. |
comparator
|
value.compare
|
direct | Overrides the data type's comparator for that column. |
keyCreator
|
value.key
|
direct | The group key a value contributes when the grid groups by that column. |
getQuickFilterText
|
value.quickFilterText
|
direct | What the quick filter matches for that column. |
cellRenderer
|
cell.render
|
direct | A built-in renderer name, a function, or a component. A bare string on the cell key is the renderer name. |
cellRendererParams
|
cell.props
|
direct | Passed to the renderer. |
cellClass
|
cell.class
|
direct | A class, an array of classes, or a function of the cell. |
cellClassRules
|
cell.classWhen
|
direct | A class per predicate, re-evaluated as values change. |
cellStyle
|
cell.style
|
direct | Inline styles, static or computed per cell. |
headerClass
|
header.class
|
direct | Adds a class to the heading cell. |
headerComponent
|
header.render
|
direct | A function or a component drawing the heading. It may write into the heading element, return an element, or return a string used as the heading text. |
editable
|
edit.enabled
|
direct | A boolean or a predicate of the cell. |
cellEditor
|
edit.editor
|
direct | A built-in editor name or one of your own. |
cellEditorParams
|
edit.props
|
direct | Passed to the editor. |
cellEditorPopup
|
edit.popup
|
direct | Open the editor over the cell rather than inside it. |
rowSelection.checkboxes
was checkboxSelection
|
selection.checkbox
|
direct | The grid generates the checkbox column rather than you declaring it on a column, so it is never exported and never appears in the tool panel. |
rowSelection.headerCheckbox
was headerCheckboxSelection
|
selection.headerCheckbox
|
direct | A tri-state select-all in the checkbox column heading. |
sortable
|
sort.enabled
|
direct | Whether the column can be sorted. |
sort
|
sort.direction
|
direct | The direction the column starts in. |
sortIndex
|
sort.order
|
direct | This column's place in a multi-column sort, lowest first. |
filter
|
filter.enabled
|
direct | Turn the column's filter on or off. The filter a column offers follows its data type. |
filterParams
|
filter.props
|
direct | Options passed to that column's filter. |
floatingFilter
|
filterRow
|
direct | filterRow draws one inline filter cell per column under the headings, and a column opts out with its own filterRow: false. |
rowGroup
|
group.enabled
|
direct | Group the rows by this column. |
rowGroupIndex
|
group.index
|
direct | Where this column sits in the grouping order. |
aggFunc
|
total
|
direct | One property drives the group row, the tree node, the pivot cell and the grand total. Split it with groupTotal and grandTotal where the subtotals and the grand total should reduce differently. |
pivot
|
pivot.enabled
|
direct | Pivot by this column. |
rowDrag
|
rowReorder.column
|
direct | Name the column the handle sits in; otherwise it goes in the first visible column. |
suppressHeaderMenuButton
|
headerControls
|
direct | 'hidden' draws no controls but keeps a sort badge on a sorted column; 'none' shows the title alone. |
API calls
What your code calls once the grid is running. 16 rows.
| AG Grid | Lattice Grid | How close | What changes |
|---|---|---|---|
api.getSelectedRows
|
selection.rows
|
direct | selection.keys() gives the keys, selection.all() includes rows the filters currently hide. |
api.selectAll
|
selection.set
|
partial | Replace the selection with the keys you pass; selection.clear() empties it. |
api.applyColumnState (sort)
|
sort.set
|
direct | An array of { col, dir } entries, in priority order, so one call sets a multi-column sort. |
api.getFilterModel
|
filters.get
|
partial | Filters read back as one condition tree whatever set them, a header popup, the tool panel or your code. |
api.setFilterModel
|
filters.set
|
partial | The condition tree is a published shape with a named operator per condition. An operator outside the published list is refused and named in a warning rather than quietly passing every row. |
api.onFilterChanged
|
filters.reapply
|
direct | Declare deps and the grid re-runs the predicate when those columns change; call filters.reapply(name) when something it reads outside the grid changes, such as a rate table arriving. |
api.applyTransaction
|
rows.apply
|
direct | Add, update and remove in one transaction, keyed on rowKey. rows.queue() batches a high-frequency feed into the next frame. |
api.applyTransactionAsync
|
rows.queue
|
direct | Batches the change into the next frame instead of applying it at once. |
api.setGridOption('rowData', rows)
|
rows.load
|
direct | Replaces the data and keeps the view: sort, filters, grouping and column layout survive. |
api.setGridOption
|
grid.set
|
direct | grid.set(key, value) writes one configuration key at run time, grid.setAll(values) several. |
api.refreshCells
|
rows.refresh
|
direct | Re-runs computed values and repaints, without re-running sort, filter or grouping. Narrow it to the rows and columns you mean. |
api.redrawRows
|
rows.refresh
|
partial | One method covers both: pass force to recompute a cached value as well as repaint. |
api.exportDataAsCsv
|
export.csv
|
direct | Fields are sanitised against formula injection on the way out. |
api.exportDataAsExcel
|
export.excel
|
direct | A real xlsx file, written without a spreadsheet dependency, and large exports stream. Included rather than sold separately. |
api.getState
|
state.get
|
direct | One serialisable object: columns, order, filters, quick text, sort, grouping, pivot, expansion, selection, scroll and paging. |
api.applyColumnState (column state)
|
state.apply
|
partial | One call restores a whole view and reports what it could not apply, such as a column that no longer exists, rather than failing silently. |
Events
One bus and one name per event, subscribed with grid.on(). 38 rows.
| AG Grid | Lattice Grid | How close | What changes |
|---|---|---|---|
onGridReady
|
ready
|
direct | Fires once, on the frame after the grid is built, with every API on it ready to call. |
onCellValueChanged
|
cell:changed
|
direct | A cell value was written by an edit, a revert, or an undo or redo step. |
onCellClicked
|
cell:clicked
|
direct | Primary button, single click. |
onCellDoubleClicked
|
cell:dblclicked
|
direct | |
onCellContextMenu
|
cell:contextmenu
|
direct | Raised by the pointer and by the keyboard menu key. |
onCellMouseOver
|
cell:mouseover
|
direct | The pointer entered a cell. Crossing between two children of one cell is not a re-entry. |
onCellMouseOut
|
cell:mouseout
|
direct | The pointer left a cell, under the same rule. |
onCellMouseDown
|
cell:mousedown
|
direct | A button was pressed on a cell, before any click is resolved. |
onRowClicked
|
row:clicked
|
direct | Raised alongside the cell event for the cell under the pointer. |
onRowDoubleClicked
|
row:dblclicked
|
direct | |
onCellEditingStarted
|
cell:edit:start
|
direct | An editor opened, by double click, by Enter, or by typing. |
onCellEditingStopped
|
cell:edit:end
|
direct | The payload says whether it committed, was cancelled, or was refused by validation. |
onRowEditingStarted
|
row:edit:start
|
direct | The whole-row counterpart. |
onRowEditingStopped
|
row:edit:end
|
direct | The whole-row counterpart. |
onSelectionChanged
|
selection:changed
|
direct | |
onCellSelectionChanged
was onRangeSelectionChanged
|
range:changed
|
direct | |
onSortChanged
|
sort:changed
|
direct | Raised whether a header click or your code changed it. |
onFilterChanged
|
filter:changed
|
direct | Covers a condition, the quick filter text and a named predicate. |
onRowGroupOpened
|
group:toggled
|
direct | One group, one branch, or all of them at once. |
onColumnResized
|
column:resized
|
direct | |
onColumnMoved
|
column:moved
|
direct | |
onColumnVisible
|
column:visible
|
direct | |
onColumnPinned
|
column:pinned
|
direct | |
onColumnRowGroupChanged
|
column:grouped
|
direct | Which columns the rows are grouped by changed. |
onColumnPivotChanged
|
column:pivoted
|
direct | |
onPaginationChanged
|
page:changed
|
direct | The page or the page size moved. |
onModelUpdated
|
model:changed
|
direct | The display model was rebuilt, with a reason naming what did it. |
onRowDataUpdated
|
rows:changed
|
direct | Rows were added, updated, removed or moved. |
onFirstDataRendered
|
render:first
|
partial | The renderer wrote its first frame. render:done reports each later pass, what caused it and how long it took. |
onGridSizeChanged
|
size:changed
|
direct | The host element changed size. |
onBodyScroll
|
scroll
|
direct | Raised only when the offset actually moved. |
onBodyScrollEnd
|
scroll:end
|
direct | The last frame of a scroll gesture has been drawn. |
onStateUpdated
|
state:changed
|
direct | One logical change, announced once, with a cause saying whether a gesture, an apply or a reset produced it. |
onRowDragEnd
|
rowDrag:ended
|
direct | The payload says whether the drop is being acted on. |
onPasteEnd
|
no equivalent | none | A paste lands as ordinary cell writes, so watch cell:changed. The whole paste is one undo entry rather than one per cell, and edit.pastePreview shows a confirm-and-cancel diff before a bulk paste commits. |
onFilterOpened
|
column:filter:open
|
direct | The header filter affordance was activated. |
onColumnMenuVisibleChanged
|
column:menu:open
|
partial | Lattice raises the request to open: the payload names the column and the element to anchor the menu to. AG Grid reports that the menu became visible or hidden. |
onCellKeyDown
|
no equivalent | none | Key handling is the grid's. Bind your own shortcuts on the host element, and use the cancellable before-events to gate what a key would do. |
Approaches
Where the answer is a shape rather than one option. 3 rows.
| AG Grid | Lattice Grid | How close | What changes |
|---|---|---|---|
no application-wide defaults (defaultColDef is per grid)
|
defaults() and columnDefaults
|
direct | Set a house configuration once and every grid built afterwards inherits it, merged beneath whatever that grid sets for itself, which always wins. defaults() reads what is in force and defaults(null) clears it. Per-column defaults stay on the grid, as columnDefaults. |
doesExternalFilterPass on a server-backed grid
|
filters.where
|
partial | A function cannot travel to a server. Give the predicate a condition twin and the source narrows the fetch itself; the grid says so rather than quietly narrowing nothing. |
Server-side pivot and group wiring
|
createPushdownSource
|
partial | A pushdown adapter turns the grid's request into what your engine speaks. The adapter declares what it can answer, the rest is finished in the browser, and lastPlan() reports the split. |
Editions and licensing
What a licence buys, on both sides. 3 rows.
| AG Grid | Lattice Grid | How close | What changes |
|---|---|---|---|
LicenseManager.setLicenseKey
|
licence
|
partial | A signed key set in configuration or through grid.licence.set(). It is checked offline and unlocks nothing, because nothing is locked: the key removes the trial watermark. |
Enterprise modules
|
no equivalent | by design | There is no feature tier. Grouping, pivot, tree data, master detail, range selection, Excel export, charts, the board, the Gantt, KPI tiles and statistics are all in the package you install. |
Community edition
|
no equivalent | by design | There is no free tier. AG Grid has one, and for a project that needs nothing beyond it that is the cheaper answer. |
What the grid tells you while you convert
Lattice Grid checks what it is handed and says so, by name, the moment it happens. Rejected input never becomes state: a key, a type, an operator or a sort entry the grid refuses is left out rather than half applied, so a saved view cannot carry the problem forward into the next session. Expect more console output than you are used to on the first run. That is the conversion telling you what is left to do, and it goes quiet as you work through it. Every one of these has a page of its own.
-
An option the grid does not read. An option that came across from the old configuration and has no meaning here is ignored rather than guessed at, and the grid names it. That list, printed on your first run, is the shortest possible to-do list for the conversion.
config.unknown:* -
A column key that configures nothing. The same on a column or a column group: the key is named, with the column it was on, so a setting that silently did nothing is visible on day one instead of in a support ticket.
*.unknown:* -
A column type that does not exist. A type the grid does not carry falls back to text and says so. Worth acting on, because a text column sorts lexically and filters with text operators, which is rarely what a number or a date column wanted.
type:* -
A filter operator a column cannot use. The condition is refused rather than admitted, so a filter the grid cannot honour never widens what a user can see. The warning names the operator it received and the operators that column accepts. In a compound filter only that leaf is dropped and every other condition still applies.
filter:op:*:* -
A saved sort naming a column that has gone. Restoring a view written against an older column set drops the entry that no longer resolves, applies the rest, and reports it. A rejected entry never reaches the saved state, so the next save cannot carry the problem forward.
grid.sort.unknown.* -
No stable row identity. Set rowKey to whatever getRowId used to return. Without it the grid identifies rows by object identity, which is enough to sort, filter and select within a session, and not enough for change tracking, streaming dedupe, selection that survives a reload, or a remote reload.
rowKey.missing -
Two columns resolving to one id. A column id defaults to its field, so two columns reading the same field collide and the second is refused. Give one of them an explicit id and both come back.
columns.duplicate:*
The full list is in every warning the grid prints, each with what it means, what the grid did about it, and what to change.
What does not translate, by design
Four things work differently here and will stay that way, so it is better to know them before you start than to find them halfway through.
- There is no free edition and no paid tier above it. AG Grid has a free community edition; we have nothing equivalent. What you get instead is one package with nothing held back: grouping, pivot, tree data, master detail, cell ranges, Excel export, charts, the board, the Gantt, KPI tiles, statistics and the AI layer are all in it, licensed per domain. See what it costs.
- Theming is CSS, not an API. Four themes ship, light, dark, high contrast and terminal, and everything past that is custom properties on the grid root. It covers a lot and it is not the same surface: AG Grid's theming API is the deeper of the two.
- The ecosystem around AG Grid is far larger. More answered questions, more third-party integrations, more people who have already hit your problem, and years more production history. Our answer to that is documentation and support, not a bigger crowd.
-
A saved AG Grid state object does not load. The two grids describe a view
differently, so plan to rebuild saved views rather than convert them: read the arrangement
you want, save it with
state.get(), and hand it back withstate.apply().
When to stay on AG Grid
There are good reasons not to move, and they are easier to weigh now than after a week of renaming.
- The free edition already covers you. If your grid needs nothing beyond AG Grid's community features and cost is the deciding factor, stay: nothing here is cheaper than free.
- You rely on something this table marks as having no equivalent. Read those rows first. If the way round one of them does not suit your application, that is a real reason to wait.
- Your team's knowledge is the asset. A team fluent in one grid, with a support contract and a working build, is worth more than a tidier configuration. Convert when you want something on the other side, not for its own sake.
If you are weighing the two rather than converting, the AG Grid comparison puts them side by side on licensing, features and support.