Pivot-style analysis inside a Modern UI screen — think a sales-by-region-by-month grid a manager can reslice on the fly — is one of the less-documented corners of the framework, and one I got asked to build for a distribution client who wanted an in-app alternative to exporting a GI to Excel every week just to pivot it manually. Here's how the pivot layout component actually works, and where its real limits are.
Two different "pivot" features — don't conflate them
Worth clearing up first, because it causes real confusion: Generic Inquiries have their own pivot-style presentation (grouping/aggregation on the Results Grid, sometimes rendered as a cross-tab in dashboard widgets). That's a GI-designer feature, built and configured entirely without code. The Modern UI pivot table component discussed here is a different thing — a qp-pivot-family layout component you place inside a custom TypeScript screen, bound to a data view, letting the end user interactively choose row/column/value fields in the browser rather than you pre-configuring the cross-tab shape in a designer. Reach for the GI version when the shape is fixed and known upfront; reach for the Modern UI pivot component when users genuinely need to reslice the same dataset different ways without you building a GI variant for each combination.
Binding the pivot component to a data view
The pivot component binds to a collection view the same way a grid does, but instead of rendering rows/columns literally as returned, it expects flat, denormalized rows that it aggregates client-side based on the user's chosen row/column/value field selections:
@graphInfo({ graphType: "MyNamespace.Graphs.SalesAnalysisInquiry", primaryView: "Results" })
export class SalesAnalysisScreen extends PXScreen {
Results = createCollection(SalesAnalysisRow);
}
class SalesAnalysisRow extends PXView {
Region = createField<PXFieldState<string>>({});
SalesPersonName = createField<PXFieldState<string>>({});
PeriodMonth = createField<PXFieldState<string>>({});
OrderTotal = createField<PXFieldState<number>>({ displayFormat: "0.00" });
}
// Layout markup binds the pivot component to Results, offering
// Region / SalesPersonName / PeriodMonth as candidate row/column
// dimensions and OrderTotal as the aggregatable value field.
The server-side graph behind this is, deliberately, just a flat query — typically a PXSelect over a projection joining orders to periods and salespeople, with no grouping done server-side at all. The framework's expectation is that the server hands over granular rows and the pivot component does the slicing entirely in the browser.
The real constraint: this is client-side aggregation, and it does not scale to large datasets
This is the point that catches people off guard, because it's the opposite of how a GI's server-side grouping behaves. A pivot component reslicing in the browser needs the full underlying row set present on the client to recompute against — asking a user to pivot "all sales transactions, ever" is asking the browser to hold and aggregate potentially hundreds of thousands of rows in memory, which will be slow or will simply hang the tab. There is no equivalent of a GI's server-side GROUP BY happening here.
I treat a reasonable row-count ceiling (a few thousand rows, tuned by testing on real client data) as a hard constraint when scoping a Modern UI pivot screen, and enforce it with mandatory server-side filter parameters — a required date range, a required region selector — rather than letting a curious user request an unbounded dataset and then wondering why the pivot freezes. If the underlying dataset is genuinely large and the client needs full-history slicing, that's a case for a proper OLAP-style solution (Power BI against an OData feed, an ARM-based financial report) rather than forcing a browser-side pivot component to do a data warehouse's job.
Number formatting and subtotal rows follow the field's own display format
Value fields aggregated in the pivot (sum, count, average — the component supports the standard aggregate functions) inherit their display formatting from the bound field's own displayFormat configuration, so a currency-formatted OrderTotal field renders its pivoted subtotals with the same formatting as it would in a plain grid — you don't configure number formatting separately for the pivoted view. Row/column subtotal and grand-total rows are a toggle on the component's configuration rather than something requiring separate aggregation logic on your part.
Export behaves like any other Modern UI grid
Because the pivot component is still, underneath, bound to a standard collection view, the platform's existing export-to-Excel action works against it the same way it works against an ordinary grid — genuinely useful, since "let me pivot it in-app, then export the specific slice I care about" is a much better user experience than exporting the entire flat dataset and pivoting manually in Excel every time, which was the exact workflow this feature replaced for the client I built it for.
Wrapping up
The Modern UI pivot component is genuinely useful for letting end users interactively reslice a bounded dataset without you pre-building every GI variant they might want — but it aggregates entirely client-side, with no server-side GROUP BY equivalent, which makes row-count discipline the single most important design constraint. Bind it to a flat, denormalized view, enforce mandatory server-side filters to keep the row count sane, and reach for a real analytics tool instead once the dataset outgrows what a browser tab should reasonably hold in memory.
Independent software engineer in Nairobi specialising in Acumatica customisations, Laravel backends, and tax fiscalisation integrations across East and Southern Africa.