Lattice Grid Buy a licence

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.

  1. Run ESLint's no-dupe-keys rule 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 two getRowStyle definitions, 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.
  2. 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.
  3. 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.
  4. 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 with state.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.