Beyond the Prompt A one-day AG Grid & Bryntum conference • 19th May 26 Learn more




Core Features

Advanced Features

JavaScript Data GridRow Grouping - Editing Groups

Enterprise

The grid supports editing grouped data when using the Client-Side Row Model. This page covers making group row cells editable, distributing edited values to descendant rows, and refreshing the grouping hierarchy after edits.

Editing Group Row Cells Copy Link

Set groupRowEditable on any column to make its group row cells editable. The grid then distributes the edited value to descendant rows using the built-in distribution. To customise this, set groupRowValueSetter to an options object, a callback, or false to disable distribution.

In the example below, double-click any group row's Sales cell to edit it. The new total spreads equally across the group's children, recursing through nested groups.

groupRowEditable accepts a boolean or a callback. The callback runs only for group rows (rowNode.group === true); leaf rows continue to honour editable. When a column sets both properties, the grid uses whichever matches the current row type, allowing separate rules for group rows and leaf rows.

groupRowEditableCopy Link
boolean | GroupRowEditableCallback
Works like editable, but is evaluated only for group rows. When provided, group rows use this property instead of editable. Set to true to make group row cells editable, or use a callback to control editability per row. When groupRowEditable is defined and no explicit groupRowValueSetter is provided, the built-in distributeGroupValue is used automatically. Columns with groupRowEditable or groupRowValueSetter do not require field or valueSetter — the group row value setter handles the edit entirely. Note: if groupRowValueSetter resolves to false or null (via distribution: false, a per-aggFunc record entry, or groupRowValueSetter: false), the cell is treated as not editable even when groupRowEditable is true.
groupRowValueSetterCopy Link
boolean | GroupRowValueSetterFunc | GroupRowValueSetterOptions
Controls how a group row value edit is distributed to descendant rows.
  • true: Uses the built-in distributeGroupValue with default settings.
  • Also enabled implicitly when groupRowEditable is defined and groupRowValueSetter is not set.
  • false: Explicitly disables group row value distribution and makes the cell not editable,
  • even if groupRowEditable is defined.
  • Function: A custom callback that receives a GroupRowValueSetterParams and pushes
  • edits down to descendants. The column does not need field or valueSetter — the callback handles the edit entirely.
  • Options object: Uses the built-in distribution logic with a GroupRowValueSetterOptions
  • configuration. When distribution resolves to false or null for the column's aggFunc, the cell is treated as not editable (overriding groupRowEditable). Fires for every setDataValue call when active, regardless of groupRowEditable.

    Built-in Value Distribution Copy Link

    The built-in distribution adjusts each child so the column's aggFunc produces the new total. Pass an options object to groupRowValueSetter to customise the strategy or precision.

    const gridOptions = {
        columnDefs: [
            {
                field: 'sales',
                aggFunc: 'sum',
                editable: true,
                groupRowEditable: true,
                // Built-in distribution: divides the sum equally among children.
                // precision: 0 rounds child values to integers and spreads the remainder.
                groupRowValueSetter: { precision: 0 },
            },
        ],
    
        // other grid options ...
    }

    Distribution Strategies Copy Link

    The distribution option controls how the edited value is spread across children. When omitted, the strategy is chosen automatically based on the column's aggFunc:

    StrategyBehaviourDefault for
    'uniform'Divides the new value equally among all children.sum
    'overwrite'Writes the new value directly to every child.avg, no aggFunc
    'percentage'Scales each child proportionally, preserving relative weights. Falls back to 'uniform' when the current total is zero.
    'increment'Distributes only the difference (newValue − oldValue) among children, adding it to current values.

    Editing is disabled by default for count, min, max, first, last, and custom aggregation functions. These functions have no unique inverse, so the grid cannot choose a safe default. To enable editing for them:

    • Set distribution to 'overwrite' to write the new value directly to every child.
    • Provide a per-aggFunc record entry with 'overwrite', true, or a custom callback (see Per-Aggregation Strategies).
    • Assign a callback to groupRowValueSetter for full control.
    const gridOptions = {
        columnDefs: [
            {
                field: 'price',
                aggFunc: 'min',
                editable: true,
                groupRowEditable: true,
                // 'overwrite' writes the edited value to every child
                groupRowValueSetter: { distribution: 'overwrite' },
            },
            {
                field: 'quantity',
                aggFunc: 'count',
                editable: true,
                groupRowEditable: true,
                // Per-aggFunc record: enable only count with overwrite
                groupRowValueSetter: { distribution: { count: 'overwrite' } },
            },
        ],
    
        // other grid options ...
    }

    Setting distribution to true uses built-in defaults for sum, avg, no-aggFunc columns, and custom aggregation functions. The count, min, max, first, and last functions stay disabled even with true; use 'overwrite', a per-aggFunc record entry, or a callback to enable them.

    Setting distribution to false or null suppresses distribution and makes the cell not editable, even when groupRowEditable is true.

    For avg, every strategy adjusts child values so the children's average equals the edited value.

    The precision option rounds the values written to child rows, not the group row's displayed value. The group value is re-computed by the column's aggFunc from the rounded children. For sum this is always exact. For avg the re-aggregated value may differ: distributing precision: 0 across 3 children produces integers, but their average (10 / 3 ≈ 3.33) is not. Use a Value Formatter to control how the group row displays the re-aggregated value.

    Distribution Options Copy Link

    Properties available on the GroupRowValueSetterOptions<TData = any, TValue = any, TContext = any> interface.

    distributionCopy Link
    Distribution strategy or per-aggregation-function strategy map. As a string: applies the chosen GroupRowValueSetterDistribution strategy to all aggregation functions. As true: enables distribution using built-in defaults for distributable aggregation functions and custom aggFuncs (which get 'overwrite'). Note: count/min/max/first/last are only enabled via explicit per-aggFunc record entries. Useful for overriding false/null from defaultColDef in deep-merge scenarios. As false or null: suppresses distribution and makes the cell not editable (overriding groupRowEditable). As a record: maps aggFunc names to individual strategies, options objects, or custom callbacks. Unmatched aggFuncs fall through to default, then to the built-in defaults.
    Example
    // Single strategy
    distribution: 'percentage'
    // Enable all aggFuncs with built-in defaults
    distribution: true
    // Per-aggFunc record (entries can be strings, objects, functions, true, or false/null)
    distribution: { sum: 'percentage', avg: 'increment', count: true, myAgg: (params) => { ... }, min: false }
    defaultCopy Link
    GroupRowValueSetterDistributionEntry
    Fallback for aggFuncs that don't have a specific distribution strategy. When distribution is a record, applies to aggFuncs not listed in the record. When distribution is omitted, applies only to custom (non-built-in) aggFuncs. Ignored when distribution is a string (all aggFuncs use the specified strategy). Accepts the same values as record entries:
  • A function for full custom handling.
  • A strategy string (e.g. 'overwrite').
  • false or null to suppress distribution and make unmatched aggFunc cells not editable.
  • An options object with strategy and precision.

  • Example
    // Custom handler
    default: (params) => {
        for (const child of params.aggregatedChildren) {
            child.setDataValue(params.column, params.newValue, 'data');
        }
    }
    // Suppress all unmatched aggFuncs
    default: false
    precisionCopy Link
    number | false
    Number of decimal places to round values written to child rows during distribution. Spreads any rounding remainder across children so their total matches exactly.
  • 0 — integers (e.g. 10 / 3[4, 3, 3])
  • 2 — two decimals (e.g. 10 / 3[3.34, 3.33, 3.33])
  • false — disable rounding (overrides auto-detect)
  • undefined (default) — auto-detect from the column definition:
  • cellEditorParams.precision if set, 0 if cellEditorParams.step is a whole number, no rounding otherwise. Note: the group row's displayed value is re-computed by the aggFunc after distribution. For sum, the sum of rounded children always honours the same precision. For other aggregation functions like avg, the re-aggregated value may not — for example, the average of integers is not necessarily an integer. Ignored for bigint columns — bigint values are always distributed as integers.
    Example
    // Round child values to integers
    colDef.groupRowValueSetter = { precision: 0 };
    // Round child values to 2 decimal places (e.g. currency)
    colDef.groupRowValueSetter = { precision: 2 };
    getValueCopy Link
    Function
    Reads a child's current value during distribution. Default: node.getDataValue(column, 'value'). Override to read from a custom data structure or computed field.
    Returns: The child's current value.
    Example
    getValue: (params) => params.data?.weight ?? 0,
    setValueCopy Link
    Function
    Writes a distributed value to a child. Default: node.setDataValue(column, value, 'data'). Override to write to a custom data structure or apply transformations.
    Returns: true if the value was changed, false otherwise.
    Example
    setValue: (params) =>
        // Apply a minimum of 0 before writing
        params.node.setDataValue(params.column, Math.max(0, Number(params.value)), 'data'),

    Merging with Default Column Definitions Copy Link

    When groupRowValueSetter is set as an options object on both defaultColDef and a column, the grid deep merges the two. The column's properties take precedence; anything not specified on the column is inherited from the default. This includes nested distribution records.

    const gridOptions = {
        defaultColDef: {
            editable: true,
            groupRowEditable: true,
            // Default: round child values to integers, disable count
            groupRowValueSetter: { precision: 0, distribution: { count: false } },
        },
        columnDefs: [
            {
                field: 'sales',
                aggFunc: 'sum',
                // Column-level override: use percentage distribution.
                // precision: 0 is inherited from defaultColDef.
                groupRowValueSetter: { distribution: 'percentage' },
            },
            {
                field: 'quantity',
                aggFunc: 'count',
                // Override defaultColDef's false with true to enable editing for count.
                groupRowValueSetter: { distribution: { count: true } },
            },
            {
                field: 'profit',
                aggFunc: 'sum',
                // No column-level override — inherits { precision: 0 } from defaultColDef.
            },
        ],
    
        // other grid options ...
    }

    Deep merging only applies when both the defaultColDef and the column define groupRowValueSetter as plain objects. If the column defines a callback function, it replaces the default entirely.

    Multiple Aggregation Functions Copy Link

    The following example mixes built-in strategies, a non-aggregated column, and custom callbacks. Double-click any group row cell to edit it:

    • Salary (sum): divides the new total equally among children, rounded to integers (precision: 0). Editing 300 across 3 children produces 100 each.
    • Bonus (avg): overwrites every child with the edited value. Editing the average to 15 sets all children's bonus to 15.
    • Projects (no aggFunc): overwrites every child with the edited value. The group cell is blank but still editable.
    • Rate % (custom callback): computes each leaf child's bonus as salary × rate / 100. Entering 20 sets each child's bonus to 20% of their salary. Uses node.getAggregatedChildren(column, true) to reach all descendant leaf rows directly.
    • SumSq (custom sumOfSquares aggFunc): a callback sets each child to √(newValue / count), the inverse of sum-of-squares.

    Per-Aggregation Strategies Copy Link

    When several columns share a groupRowValueSetter (e.g. via defaultColDef) but use different aggregation functions, set distribution to a record keyed by aggFunc name. Each entry can be a strategy string, an options object, a custom callback, true for built-in defaults, or false/null to suppress distribution for that aggFunc:

    const myCustomAgg = (params) => {
        let total = 0;
        for (const value of params.values) {
            total += Number(value) || 0;
        }
        return total;
    };
    
    // Optional: inverse enables incremental aggregation on row changes
    myCustomAgg.inverse = (params) => {
        let total = Number(params.result) || 0;
        for (const value of params.values) {
            total -= Number(value) || 0;
        }
        return total;
    };
    
    const gridOptions = {
        aggFuncs: {
            myCustomAgg,
        },
        defaultColDef: {
            editable: true,
            groupRowEditable: true,
            groupRowValueSetter: {
                precision: 0,
                distribution: {
                    sum: 'percentage',                          // strategy string
                    avg: { distribution: 'increment' },         // options object
                    myCustomAgg: (params) => {                  // custom callback function
                        for (const child of params.aggregatedChildren) {
                            child.setDataValue(params.column, params.newValue, 'data');
                        }
                        return true;
                    },
                },
            },
        },
    
        // other grid options ...
    }

    Custom aggregation functions are disabled by default. To enable editing, add an explicit entry in the distribution record. The entry can be a strategy string, an options object, or a callback that implements the inverse of the aggregation logic:

    const gridOptions = {
        defaultColDef: {
            editable: true,
            groupRowEditable: true,
            groupRowValueSetter: {
                precision: 0,
                distribution: {
                    sum: 'percentage',
                    avg: 'increment',
                    count: 'overwrite',
                    // Custom aggFunc: provide a callback that reverses the aggregation
                    myCustomAgg: (params) => {
                        for (const child of params.aggregatedChildren) {
                            child.setDataValue(params.column, params.newValue, 'data');
                        }
                        return true;
                    },
                },
            },
        },
    
        // other grid options ...
    }

    Aggregation functions not listed in the record fall through to the default fallback, then to the built-in defaults (see the strategy table above).

    Default Fallback Copy Link

    When multiple custom aggregation functions share the same distribution logic, the default property avoids repeating entries for each one. It applies to any aggregation function not explicitly listed in the distribution record, including custom aggregations (which are otherwise disabled) and columns with no aggFunc (which already default to 'overwrite'). Non-distributable functions (count, min, max, first, last) are not affected by default and must always be listed explicitly.

    const gridOptions = {
        defaultColDef: {
            editable: true,
            groupRowEditable: true,
            groupRowValueSetter: {
                precision: 0,
                distribution: {
                    sum: 'percentage',
                    count: 'overwrite',
                },
                // Fallback for unlisted custom aggFuncs and no-aggFunc columns.
                default: 'overwrite',
            },
        },
    
        // other grid options ...
    }

    Custom getValue and setValue Copy Link

    The getValue and setValue callbacks override how the built-in distributor reads from and writes to child rows. By default it uses node.getDataValue(column, 'value') to read and node.setDataValue(column, value, 'data') to write. Provide your own when child values live on a different field or custom data structure, when you need to apply transformations or side effects during distribution, or when the column's valueGetter / valueSetter are not suitable for distribution.

    const gridOptions = {
        columnDefs: [
            {
                field: 'amount',
                aggFunc: 'sum',
                editable: true,
                groupRowEditable: true,
                groupRowValueSetter: {
                    distribution: 'percentage',
                    getValue: (params) => (params.data && params.data.weight) || 0,
                    setValue: (params) => params.node.setDataValue(params.column, params.value, 'data'),
                },
            },
        ],
    
        // other grid options ...
    }

    Custom Distribution with a Callback Copy Link

    For full control over how group-level edits cascade, assign a function to groupRowValueSetter. The callback receives a GroupRowValueSetterParams object and should return true when any child value was changed.

    The example below distributes the edited total equally among children, rounded to integers (largest remainder method). It calls setDataValue on each child; aggregation then refreshes parent totals automatically.

    GroupRowValueSetterParams Copy Link

    Properties available on the GroupRowValueSetterParams<TData = any, TValue = any, TContext = any> interface.

    The grid api.
    Application context as set on gridOptions.context.
    Column for this callback.
    ColDef provided for this column.
    The value before the change.
    The value after the change.
    The group row node being edited.
    Row data for the group node. null or undefined for grouping groups or tree data filler nodes.
    eventSourceCopy Link
    string | undefined
    What triggered the edit (e.g. 'ui', 'undo', 'paste').
    valueChangedCopy Link
    boolean
    Whether the aggregated value actually changed compared to the previous value.
    aggregatedChildrenCopy Link
    The immediate children that contribute to this group's aggregation.
  • Leaf groups (groups directly containing data rows): the data rows themselves.
  • Non-leaf groups (groups containing sub-groups): the child group rows.
  • Calling setDataValue() on a child group cascades the edit recursively through the full hierarchy. The built-in distributeGroupValue does this automatically.
  • Pivot mode: only rows matching the edited pivot column's keys are included.
  • Use rowNode.getAggregatedChildren(colKey) to retrieve the same children programmatically. Pass true as the second argument to collect all descendant leaf rows recursively. Only supported with the Client-Side Row Model.

    Aggregated Children Copy Link

    The groupRowValueSetter callback receives an aggregatedChildren array: the immediate children that contribute to the Aggregation for the edited column. Cascading edits to these children avoids traversing the row hierarchy by hand.

    What aggregatedChildren returns depends on the column and grid configuration:

    • Regular value columns: the direct children used for aggregation. This respects suppressAggFilteredOnly and groupAggFiltering; when those cause aggregation to include all children rather than just filtered ones, aggregatedChildren follows suit.
    • Pivot columns on leaf groups: only the children matching the column's pivot keys, since pivot aggregation groups rows by pivot key values.
    • Non-group rows: an empty array.

    The same children can be retrieved programmatically with rowNode.getAggregatedChildren(colKey). To collect all descendant leaf rows instead of only the immediate children, pass true as the second argument: rowNode.getAggregatedChildren(colKey, true).

    See Retrieving Aggregated Children for more details.

    Cascading Edits Copy Link

    Key points for implementing cascading edits:

    • Call rowNode.setDataValue on each child to write the new value down.
    • When a child is itself a group with its own groupRowValueSetter, the cascade recurses further.
    • Parent aggregates refresh automatically: child data changes re-run the column aggFunc, so editing a group total rebalances the children to match it.
    • Aggregation is batched. All setDataValue calls within a single groupRowValueSetter invocation are processed before re-aggregation runs.

    The aggregatedChildren parameter and rowNode.getAggregatedChildren() method are only supported with the Client-Side Row Model. For other row models, aggregatedChildren is an empty array.

    Calling distributeGroupValue Copy Link

    The built-in distribution function distributeGroupValue is exported from ag-grid-enterprise and can be called directly inside a custom groupRowValueSetter callback. This is useful when custom logic is needed before or after the distribution:

    import { distributeGroupValue } from 'ag-grid-enterprise';
    
    colDef.groupRowValueSetter = (params) => {
        // Custom pre-processing
        const adjusted = Math.max(0, Number(params.newValue));
        return distributeGroupValue({ ...params, newValue: adjusted }, { distribution: 'percentage' });
    };
    

    You can also assign distributeGroupValue directly without a wrapper for default behaviour:

    import { distributeGroupValue } from 'ag-grid-enterprise';
    
    colDef.groupRowValueSetter = distributeGroupValue;
    

    Editing Pivot Columns Copy Link

    When using Pivoting, the groupRowValueSetter also works with pivot result columns. The key difference is that aggregatedChildren returns only the children matching the column's pivot keys, not all children of the group.

    The following example uses the built-in distribution in pivot mode. Setting groupRowEditable enables distribution automatically. Double-click any pivot cell to edit it; the edited value is distributed uniformly among the children matching that pivot column's keys.

    For full control, use a custom groupRowValueSetter callback. In the example below:

    • The grid is pivoted by product (Electronics, Clothing, Food), grouped by region and country.
    • Double-clicking any pivot column cell opens the editor for the aggregated value.
    • When editing a pivot cell (e.g. "Electronics" on the "Europe" row), aggregatedChildren contains only the European rows selling Electronics, not all European rows.
    • The edit is distributed equally among those matching children, leaving other product categories unchanged.

    Edits on pivot columns therefore affect only the rows contributing to that pivot value, leaving other pivot categories untouched.

    Tree Data Copy Link

    groupRowEditable and groupRowValueSetter also work with Tree Data. Parent nodes act as group rows, so editing a parent's aggregated value cascades the change down to its children just as with row grouping.

    The example below shows a company's budget organised as a tree: departments contain teams, and teams contain employees. Editing a department's or team's budget distributes the new total equally among its members, rounded to integers, recursing through the hierarchy.

    Refreshing Groups After Editing Copy Link

    When grouped columns are editable, setting refreshAfterGroupEdit=true causes the grid to update row data and recalculate the grouping after every committed edit. Without this option, the row data updates but the grouping does not get updated until the next full refresh.

    When enabling refreshAfterGroupEdit, also provide getRowId so that the grid can track rows by stable IDs while rebuilding the grouping hierarchy.

    The following example demonstrates this behaviour. Double-click on a department or team cell to edit it; the grid re-evaluates the grouping and moves the row to the correct group instantly.

    See also Read Only Edit for configuring immutable grouped data or connecting the grid with a store.