Skip to content

Server Side Grouping

interface ServerSideGroupingLoadRowsRequest {
/**
* Group route being loaded. The root route is an empty array.
*/
route: ServerSideGroupRoute;
/**
* Route depth. Root is level 0, first-level groups are level 1.
*/
level: number;
/**
* Active grouping columns in server request order.
*/
groupBy: ColumnProp[];
/**
* Offset inside this route, aligned to the configured block size.
*/
start: number;
/**
* Maximum number of direct route children requested.
*/
limit: number;
/**
* Active remote sort model.
*/
sort?: ServerSideGroupingSort;
/**
* Active remote column filter model.
*/
filter?: ServerSideGroupingFilter;
/**
* Optional global quick-filter payload forwarded to the server.
*/
quickFilter?: ServerSideGroupingQuickFilter;
levelParams?: unknown;
parent?: ServerSideGroupRow
}

ServerSideGroupingLoadExpandedGroupsRequest

Section titled “ServerSideGroupingLoadExpandedGroupsRequest”
interface ServerSideGroupingLoadExpandedGroupsRequest {
/**
* Active grouping columns in server request order.
*/
groupBy: ColumnProp[];
/**
* Active remote sort model.
*/
sort?: ServerSideGroupingSort;
/**
* Active remote column filter model.
*/
filter?: ServerSideGroupingFilter;
/**
* Optional global quick-filter payload forwarded to the server.
*/
quickFilter?: ServerSideGroupingQuickFilter
}

interface ServerSideGroupingConfig {
/**
* Column props used as grouping levels. The server decides returned groups for each level.
*/
groupBy: ColumnProp[];
/**
* Loads one block of groups or rows for a route. The AbortSignal is cancelled on refresh/config changes.
*/
loadRows: ServerSideGroupingLoadRows<T>;
/**
* Optional one-shot datasource used by `expandAllGroups`.
*
* Return every group row that should be expanded with its full `route`. This
* avoids recursive route loading for backends that can compute the group tree
* in one request.
*/
loadExpandedGroups?: ServerSideGroupingLoadExpandedGroups;
/**
* Number of direct route children requested per block.
*/
blockSize?: number;
/**
* Purge child route cache when a group is collapsed.
*/
purgeClosedGroups?: boolean;
/**
* @deprecated Use `purgeClosedGroups`.
*/
purgeOnCollapse?: boolean;
/**
* Expands routes unless the user explicitly collapses them. A predicate can target selected routes.
*/
openGroupsByDefault?: boolean | ((route: ServerSideGroupRoute) => boolean);
/**
* Initial externally controlled expanded routes.
*/
expandedRoutes?: ServerSideGroupRoute[] | Set<string>;
/**
* Limits simultaneous remote block loads.
*/
maxConcurrentRequests?: number;
/**
* Debounces block load execution. Useful when virtualization emits several scroll updates quickly.
*/
blockLoadDebounceMs?: number;
/**
* Optional global quick-filter payload included in every request.
*/
quickFilter?: ServerSideGroupingQuickFilter;
/**
* Enables plugin debug helpers.
*/
debug?: boolean | ServerSideGroupingDebugConfig;
getLevelParams?: (
route: ServerSideGroupRoute,
level: number,
parent?: ServerSideGroupRow,
) => unknown;
formatAggregate?: (
key: string,
value: unknown,
group: ServerSideGroupRow,
) => string;
formatGroupRow?: (
group: ServerSideGroupRow,
context: {
route: ServerSideGroupRoute;
level: number;
expanded: boolean;
groupBy: ColumnProp[];
},
) => string;
groupRowRenderer?: GroupLabelTemplateFunc;
/**
* Receives the imperative API while the plugin is active and `null` on cleanup.
*/
api?: (api: ServerSideGroupingApi | null) => void
}

HTMLRevoGridElement (Extended from global)

Section titled “HTMLRevoGridElement (Extended from global)”
interface HTMLRevoGridElement {
serverSideGrouping?: Partial<ServerSideGroupingConfig>;
'server-side-grouping'?: Partial<ServerSideGroupingConfig>
}

AdditionalData (Extended from @revolist/revogrid)

Section titled “AdditionalData (Extended from @revolist/revogrid)”
interface AdditionalData {
/**
* @deprecated Use `grid.serverSideGrouping` instead.
*/
serverSideGrouping?: Partial<ServerSideGroupingConfig>
}
  • Event integration groupexpandclick, viewportscroll: Uses core grouping row rendering and viewport events to load grouped server-side blocks.
  • Event integration beforefilterapply, beforesortingapply: Forwards sorting and filtering metadata to server-side grouping requests.
class ServerSideGroupingPlugin {
destroy();
}

Resolves user config into a complete internal config object.

This is the single place for defaults and deprecated alias handling, including purgeOnCollapse -> purgeClosedGroups.

export function normalizeServerSideGroupingConfig<T extends DataType>(
config: Partial<ResolvedServerSideGroupingConfig<T>> =;

Builds the server request filter model from the RevoGrid filter event.

The advanced filterItems model is preferred because the legacy collection only contains the first filter per column. Values are cloned into a stable, serializable shape so request logs, cache keys, and REST bodies can represent Set-backed selection filters and nested slider ranges consistently.

export function normalizeServerSideGroupingFilter(
detail?: ServerSideGroupingFilterEventDetail,
): ServerSideGroupingFilter | undefined;

class ServerSideGroupingModel {
configure(config: ResolvedServerSideGroupingConfig<T>, preserveExpanded = true);
setSort(sort?: ServerSideGroupingSort);
setFilter(filter?: ServerSideGroupingFilter);
setQuickFilter(quickFilter?: ServerSideGroupingQuickFilter);
reset(preserveExpanded = true);
async loadRoot();
async expand(routeOrId: ServerSideGroupId);
async expandAllGroups();
collapse(routeOrId: ServerSideGroupId);
collapseAllLoadedGroups();
async refreshServerSide();
async refreshRoute(routeOrId: ServerSideGroupId);
purgeRoute(routeOrId: ServerSideGroupId = ROOT_ROUTE);
destroy();
async retryRoute(routeOrId: ServerSideGroupId);
async loadRouteBlock(routeOrId: ServerSideGroupId, start = 0, force = false): Promise<boolean>;
getMissingBlockRequests(rows: DataType[]);
materialize(displayProp?: ColumnProp): DataType[];
getCacheState(): ServerSideGroupingCacheState;
getRouteKey(route: ServerSideGroupRoute);
}

Builds a stable cache key for a route. Route keys are intentionally JSON strings so they round-trip through the public API and can distinguish nested values safely.

export function createServerSideGroupRouteKey(route: ServerSideGroupRoute): string;

Converts a public group id into a route array. Accepts real route arrays, serialized route keys returned by createServerSideGroupRouteKey, or a single key.

export function parseServerSideGroupId(id: ServerSideGroupId): ServerSideGroupRoute;

export type ServerSideGroupKey = string | number | boolean | null;

export type ServerSideGroupRoute = ServerSideGroupKey[];

export type ServerSideGroupId = string | ServerSideGroupRoute;

export type ServerSideGroupKind = 'group' | 'row' | 'loading' | 'skeleton' | 'error';

export type ServerSideGroupingStatus = 'idle' | 'loading' | 'loaded' | 'error';

export type ServerSideGroupingSort = Record<ColumnProp, Exclude<Order, undefined>>;

Serializable server-side filter item forwarded to loadRows.

It intentionally mirrors RevoGrid filter metadata while replacing runtime-only values such as Set with JSON-friendly arrays. Pro filters such as selection and slider can therefore be sent directly to REST/SQL backends.

/**
* Serializable server-side filter item forwarded to `loadRows`.
*
* It intentionally mirrors RevoGrid filter metadata while replacing runtime-only
* values such as `Set` with JSON-friendly arrays. Pro filters such as selection
* and slider can therefore be sent directly to REST/SQL backends.
*/
export type ServerSideGroupingFilterItem =
| FilterCollectionItem
| Pick<FilterData, 'id' | 'type' | 'value' | 'relation' | 'hidden'>;

Server filter model keyed by column prop. A prop can contain one item for the legacy filter collection path or multiple items for the advanced filter panel.

/**
* Server filter model keyed by column prop. A prop can contain one item for the
* legacy filter collection path or multiple items for the advanced filter panel.
*/
export type ServerSideGroupingFilter = Record<
ColumnProp,
ServerSideGroupingFilterItem | ServerSideGroupingFilterItem[]
>;

export type ServerSideGroupingTotals = Record<string, unknown>;

export type ServerSideGroupingQuickFilter = string | Record<string, unknown>;

interface ServerSideGroupingDebugConfig {
/**
* Renders a lightweight cache summary below the grid while grouping is active.
*/
cacheView?: boolean
}

interface ServerSideGroupRow {
key: ServerSideGroupKey;
/**
* Optional display label. Falls back to `key`.
*/
label?: string;
/**
* Count displayed on the group row. Usually total descendant leaf rows.
*/
count?: number;
/**
* Known number of direct children for this group route. At the final grouping
* level this is usually the number of leaf rows.
*/
childCount?: number;
/**
* Whether this group can be expanded. Defaults to true while there are
* more grouping columns, and false at leaf level unless explicitly set.
*/
hasChildren?: boolean;
/**
* Totals or aggregates associated with this group row.
*/
aggregates?: ServerSideGroupingTotals;
totals?: ServerSideGroupingTotals;
/**
* Server-provided params forwarded when this group route is loaded.
*/
levelParams?: unknown
}

ServerSideExpandedGroupRow (Extended from index.ts)

Section titled “ServerSideExpandedGroupRow (Extended from index.ts)”
interface ServerSideExpandedGroupRow {
/**
* Full route for this group row. For example `['Germany', 'Swimming']`.
*/
route: ServerSideGroupRoute
}

ServerSideGroupingLoadExpandedGroupsResult

Section titled “ServerSideGroupingLoadExpandedGroupsResult”
interface ServerSideGroupingLoadExpandedGroupsResult {
/**
* Flattened list of all group rows that should be expanded.
*
* Parent routes are inferred from each route. The plugin rebuilds route
* blocks from this list and keeps leaf-row blocks unloaded.
*/
groups: ServerSideExpandedGroupRow[];
grandTotals?: ServerSideGroupingTotals
}

interface ServerSideGroupingLoadRowsResult {
groups?: ServerSideGroupRow[];
rows?: T[];
/**
* Known direct child count for this route. For grouped routes this is the
* number of direct child groups. For leaf routes this is the number of rows.
*/
rowCount?: number;
/**
* Alias for `rowCount` for server APIs that return `total`.
*/
total?: number;
/**
* Whether more direct children can be requested after this block.
*/
hasMore?: boolean;
totals?: ServerSideGroupingTotals;
grandTotals?: ServerSideGroupingTotals
}

export type ServerSideGroupingLoadRows<T extends DataType = DataType> = (
request: ServerSideGroupingLoadRowsRequest,
signal?: AbortSignal,
) => Promise<ServerSideGroupingLoadRowsResult<T> | T[] | ServerSideGroupRow[]>;

export type ServerSideGroupingLoadExpandedGroups = (
request: ServerSideGroupingLoadExpandedGroupsRequest,
signal?: AbortSignal,
) => Promise<ServerSideGroupingLoadExpandedGroupsResult | ServerSideExpandedGroupRow[]>;

interface ServerSideGroupingCacheRouteState {
route: ServerSideGroupRoute;
status: ServerSideGroupingStatus;
rowCount?: number;
hasMore?: boolean;
loadingBlocks: number[];
loadedBlocks: number[];
error?: unknown
}

interface ServerSideGroupingCacheState {
groupBy: ColumnProp[];
expandedRoutes: ServerSideGroupRoute[];
routeCount: number;
routes: ServerSideGroupingCacheRouteState[];
grandTotals?: ServerSideGroupingTotals;
activeRequests: number;
queuedRequests: number
}

interface ServerSideGroupingApi {
/**
* Expands a group route and lazily loads the first child block when needed.
*/
expandGroup(id: ServerSideGroupId): Promise<void>;
/**
* Expands all server group routes.
*
* If `loadExpandedGroups` is configured this uses one backend request to
* hydrate all group routes. Otherwise it recursively loads group blocks.
* Leaf-row routes are opened but not fully loaded; normal viewport loading
* fetches visible row blocks.
*/
expandAllGroups(): Promise<void>;
/**
* Collapses a group route. Cache retention is controlled by `purgeClosedGroups`.
*/
collapseGroup(id: ServerSideGroupId): void;
/**
* Collapses all group routes currently known by the cache.
*/
collapseAllGroups(): void;
/**
* Clears the server-side grouping cache and reloads the root route.
*/
refreshServerSide(): Promise<void>;
/**
* Clears and reloads one route branch without resetting the whole grid.
*/
refreshRoute(route: ServerSideGroupId): Promise<void>;
/**
* Purges all cached blocks or the requested route branch.
*/
purgeServerSideCache(route?: ServerSideGroupId): void;
/**
* Returns a serializable snapshot of routes, loaded blocks, and queue state.
*/
getServerSideCacheState(): ServerSideGroupingCacheState
}

ResolvedServerSideGroupingConfig (Extended from index.ts)

Section titled “ResolvedServerSideGroupingConfig (Extended from index.ts)”
interface ResolvedServerSideGroupingConfig {
blockSize: number;
purgeClosedGroups: boolean;
purgeOnCollapse: boolean;
openGroupsByDefault: boolean | ((route: ServerSideGroupRoute) => boolean);
expandedRoutes?: ServerSideGroupRoute[] | Set<string>;
maxConcurrentRequests: number;
blockLoadDebounceMs: number
}

ServerSideGroupingSyntheticRow (Extended from index.ts)

Section titled “ServerSideGroupingSyntheticRow (Extended from index.ts)”
interface ServerSideGroupingSyntheticRow {
__serverSideGrouping?: true;
__serverSideGroupingKind: ServerSideGroupKind;
__serverSideGroupingRoute: ServerSideGroupRoute;
__serverSideGroupingRouteKey: string;
__serverSideGroupingIndex?: number;
__serverSideGroupingBlockStart?: number;
__serverSideGroupingError?: unknown
}

Creates the public imperative API from plugin-local handlers.

export function createServerSideGroupingApi(
handlers: ServerSideGroupingApiHandlers,
): ServerSideGroupingApi;

Plugin methods used to construct the public server-side grouping API.

interface ServerSideGroupingApiHandlers {
/** Expands and loads a route when needed. */
expandGroup(id: ServerSideGroupId): Promise<void>;
/** Expands every server group route, loading group blocks recursively. */
expandAllGroups(): Promise<void>;
/** Collapses a route and optionally purges child cache based on config. */
collapseGroup(id: ServerSideGroupId): void;
/** Collapses every loaded group route. */
collapseAllGroups(): void;
/** Clears all route blocks and reloads the root route. */
refreshServerSide(): Promise<void>;
/** Clears one route branch and reloads it when visible/expanded. */
refreshRoute(route: ServerSideGroupId): Promise<void>;
/** Purges all cache or one route branch without forcing an immediate load. */
purgeServerSideCache(route?: ServerSideGroupId): void;
/** Returns a serializable cache snapshot for diagnostics. */
getServerSideCacheState(): ServerSideGroupingCacheState
}

Marks the first visible column as grouping-capable so RevoGrid’s core grouping row renderer can draw server-side synthetic group rows.

export function markServerSideGroupingColumn(
columnsByType: Record<string, ColumnRegular[]> | undefined,
refreshByType: (type: ColumnType) => void,
);

Adds or removes the debug cache view from RevoGrid’s extra vnode registry.

export function syncServerSideGroupingDebugCacheView(;

Removes the plugin-owned debug vnode and returns the remaining vnode list.

export function removeServerSideGroupingDebugCacheView(revogrid: HTMLRevoGridElement);

Inputs used to register the optional cache diagnostics vnode.

interface ServerSideGroupingDebugViewOptions {
/** Grid element that owns the extra vnode registry. */
revogrid: HTMLRevoGridElement;
/** RevoGrid hyperscript renderer passed to plugin extra vnodes. */
h: HyperFunc<VNode>;
/** Whether the diagnostics vnode should be present. */
enabled: boolean;
/** Reads current cache state without coupling this helper to the model. */
getCacheState(): ServerSideGroupingCacheState
}

addServerSideGroupingExcelExportTransformer

Section titled “addServerSideGroupingExcelExportTransformer”

Prepends the grouping export transformer to a one-off export config.

export function addServerSideGroupingExcelExportTransformer<T extends;

Normalizes server-side grouping rows before Excel providers receive them.

Group rows are exportable, but loading/skeleton/error placeholders are UI state and should not become workbook rows. Group labels are copied into the first visible export column so exports match what users see in the grid.

transformServerSideGroupingExcelExport: ExcelExportContextTransformer;

Full filter payload emitted by RevoGrid before local filter trimming.

collection is the backward-compatible single-filter model. filterItems is the complete advanced model used by Pro filters, including selection, quick-search, slider, and multi-filter relations.

interface ServerSideGroupingFilterEventDetail {
collection?: Record<ColumnProp, FilterCollectionItem>;
filterItems?: MultiFilterItem
}

Applies a loaded block to a route and returns optional grand totals.

This function owns response normalization, group-vs-leaf child shaping, known/unknown count inference, and hasMore calculation.

export function applyServerSideGroupingLoadResult<T extends DataType>(;

Inputs required to merge one datasource response into a route block.

interface ApplyLoadResultOptions {
/** Route state receiving the loaded block. */
state: RouteState<T>;
/** Route-local block start offset. */
blockStart: number;
/** Raw datasource response, including legacy array-only forms. */
response: ServerSideGroupingLoadRowsResult<T> | T[] | ServerSideGroupRow[];
/** Resolved plugin config used to infer grouping level and block size. */
config: ResolvedServerSideGroupingConfig<T>;
/** Route creator callback used when returned group rows introduce child routes. */
ensureRoute(route: ServerSideGroupRoute, parent?: ServerSideGroupRow): RouteState<T>
}

Converts one route plus any expanded descendants into RevoGrid source rows.

The output includes synthetic core grouping rows, loading rows, skeleton rows, error rows, and final leaf rows. It does not perform network loading.

export function materializeServerSideGroupingRoute<T extends DataType>(
state: RouteState<T>,
displayProp: ColumnProp,
includeGroupRows: boolean,
context: ServerSideGroupingMaterializeContext<T>,
): DataType[];

Callbacks and config required to convert route state into grid rows.

interface ServerSideGroupingMaterializeContext {
/** Resolved grouping config used for row labels, block size, and grouping depth. */
config: ResolvedServerSideGroupingConfig<T>;
/** Ensures nested routes exist when expanded group rows are materialized. */
ensureRoute(route: ServerSideGroupRoute, parent?: ServerSideGroupRow): RouteState<T>;
/** Returns the current expansion state for a serialized route key. */
isRouteExpanded(routeKey: string): boolean
}

Scans currently visible materialized rows and returns unloaded route blocks.

The plugin uses this after scroll/render events to lazily load only blocks represented by visible loading or skeleton rows.

export function getMissingServerSideGroupingBlockRequests(
rows: DataType[],
routes: Map<string, RouteState>,
);

SERVER_SIDE_GROUPING_PLUGIN: string;

Normalizes AbortError detection across browser DOMException and plain error objects.

export function isAbortError(error: unknown);

Coordinates block request concurrency, debounce, and cancellation for a model instance.

The queue is intentionally independent from route state. It answers only: “when may a request run?” and “which currently running requests should be aborted?”

class ServerSideGroupingRequestQueue {
/**
* Aborts all active requests and releases queued waiters so stale model work
* can exit through generation checks.
*/
cancelPendingRequests();
/**
* Creates and tracks an AbortController for one datasource call.
*/
createAbortController();
/**
* Stops tracking a controller after its datasource call settles.
*/
releaseAbortController(controller: AbortController);
/**
* Waits until a concurrency slot is available and returns an idempotent release function.
*/
async acquireRequestSlot();
/**
* Applies configured debounce before a block request reaches the datasource.
*/
async waitForBlockLoadDebounce();
}

Checks whether a serialized route belongs to a route branch. Used by partial refresh and purge so those operations can target one group without clearing siblings.

export function isDescendantRouteKey(routeKey: string, parentRoute: ServerSideGroupRoute);

Aligns any child index to the start offset for its server block.

export function toServerSideGroupingBlockStart(index: number, blockSize: number);

Canonical route for top-level group rows. All server-side grouping loads start here.

ROOT_ROUTE: ServerSideGroupRoute;

Internal fallback cell property used when the grid has no visible grouping column.

MESSAGE_PROP: string;

Creates empty route state and seeds known child counts from parent metadata.

export function createRouteState<T extends DataType>(
route: ServerSideGroupRoute,
config: ResolvedServerSideGroupingConfig<T>,
parent?: ServerSideGroupRow,
): RouteState<T>;

Clears loaded data and error/loading metadata while keeping the route object and parent metadata alive for partial refreshes.

export function clearRouteState(state: RouteState);

Returns the number of child slots to materialize for a route. Known-count routes render exactly rowCount; unknown-count routes render up to the largest loaded block and may add a tail loader separately.

export function getMaterializedChildCount(state: RouteState);

Resolves a child at an absolute route-local index from the block cache.

export function getChildAt<T extends DataType>(
state: RouteState<T>,
index: number,
blockSize: number,
);

Converts mutable internal route state into the public serializable cache view.

export function toCacheRouteState(route: RouteState): ServerSideGroupingCacheRouteState;

A direct child materialized for a route block. Group children point to their child route, while row children are final leaf records from the backend.

/**
* A direct child materialized for a route block. Group children point to their
* child route, while row children are final leaf records from the backend.
*/
export type MaterializedChild<T extends DataType = DataType> =
| { kind: 'group'; group: ServerSideGroupRow; route: ServerSideGroupRoute }
| { kind: 'row'; row: T };

Mutable cache state for one route.

A route is the unit of loading, expansion, retry, purge, and refresh. Blocks are keyed by their start offset and contain only direct children for this route.

interface RouteState {
/** Route represented by this state. Root is an empty array. */
route: ServerSideGroupRoute;
/** Stable serialized route key used in maps and synthetic row metadata. */
routeKey: string;
/** Parent group row returned by the backend, if this is not root. */
parent?: ServerSideGroupRow;
/** Server-provided params forwarded when this route is loaded. */
levelParams?: unknown;
/** Loaded blocks of direct child groups or leaf rows keyed by block start. */
blocks: Map<number, MaterializedChild<T>[]>;
/** Block starts that currently have in-flight load requests. */
loadingBlocks: Set<number>;
/** Per-block request versions used to ignore stale responses. */
blockVersions: Map<number, number>;
/** Monotonic route request version. Incrementing invalidates previous block loads. */
requestVersion: number;
/** Current route loading state used by materialization. */
status: 'idle' | 'loading' | 'loaded' | 'error';
/** Known direct child count. Undefined means unknown count. */
rowCount?: number;
/** Unknown-count continuation flag from the backend. */
hasMore?: boolean;
/** Route-level totals returned by the backend. */
totals?: ServerSideGroupingTotals;
/** Last route/block load error. */
error?: unknown;
/** Failed block start. Retry targets this block only. */
errorBlockStart?: number
}