
Pvotly is a framework-agnostic pivot table library that transforms flat records into cross-tab reports with drag-and-drop field arrangement, multiple aggregations, filtering, sorting, and export.
You can use it as a React component, a vanilla JavaScript widget, or as a pure headless engine that computes the grid for a custom renderer.
Features:
- Generates cross-tab reports from object records, matrices, CSV text, and async data sources.
- Renders a drag-and-drop field list for Rows, Columns, Values, and Report Filters.
- Aggregates values with sum, count, distinct count, average, median, minimum, maximum, product, first, last, variance, and standard deviation.
- Calculates custom measures from arithmetic formulas and aggregate references.
- Sorts field members or entire axes by aggregated measure values.
- Filters members by selection, labels, values, numeric ranges, and date ranges.
- Groups date fields into year, quarter, month, week, day, and time hierarchies.
- Displays grand totals and subtotals in compact, classic, or flat layouts.
- Styles matching cells through measure-specific conditional formatting rules.
- Exports the current grid as CSV, Excel-compatible markup, HTML, JSON, or a print view.
- Switches between light and dark themes and accepts runtime design-token overrides.
- Exposes a headless grid model for custom DOM, canvas, server, or framework renderers.
Use Cases:
- Business intelligence dashboards that need to summarize sales, inventory, or marketing data with drag-and-drop pivot tables inside a React admin panel.
- Customer analytics platforms where support agents filter support ticket volumes by region, priority, and resolution time using a headless engine with a custom chart renderer.
- Embedded reporting in a SaaS product that lets users upload CSV files, arrange dimensions, and export formatted cross-tabs to Excel.
- Logistics monitoring tools that group shipment delays by carrier and month, using date parts to drill from year down to day without extra data preprocessing.
- Financial planning interfaces where analysts apply conditional formatting to highlight budget vs. actual variances and share the view via the print/PDF export.
How To Use It:
Installation
Choose the package that matches the rendering layer in your project. @pvotly/web and @pvotly/react re-export the core engine and its shared types.
# Vanilla JavaScript or TypeScript with the bundled UI. npm install @pvotly/web # React application with the bundled UI. npm install @pvotly/react @pvotly/web # Headless pivot engine only. npm install @pvotly/core
Import the shared stylesheet once when you use the DOM widget or React component.
import '@pvotly/web/styles.css';
Basic Usage
Get started with a small set of object records and one clear row, column, and measure mapping. The DOM widget mounts into a selector or an existing HTMLElement.
<div id="revenue-report"></div>
import { PivotTable } from '@pvotly/web';
import '@pvotly/web/styles.css';
const revenueRecords = [
{ region: 'West', product: 'Laptop', sales: 18400, orders: 36 },
{ region: 'West', product: 'Monitor', sales: 7200, orders: 48 },
{ region: 'East', product: 'Laptop', sales: 15600, orders: 31 },
{ region: 'East', product: 'Monitor', sales: 6900, orders: 46 },
];
const report = new PivotTable('#revenue-report', {
height: 460,
dataSource: { data: revenueRecords },
slice: {
rows: [{ uniqueName: 'region' }],
columns: [{ uniqueName: 'product' }],
measures: [
{ uniqueName: 'sales', aggregation: 'sum', caption: 'Sales' },
{ uniqueName: 'orders', aggregation: 'sum', caption: 'Orders' },
],
},
options: {
grid: {
type: 'compact',
showGrandTotals: 'on',
},
},
});
report.on('cellClick', ({ cell }) => {
console.log(cell.formatted);
});
The widget displays its toolbar and field-list panel by default. Drag a field between Rows, Columns, Values, and Report Filters to update the report definition.
Advanced Usages
Load pvotly from a CDN
<link rel="stylesheet" href="https://unpkg.com/@pvotly/[email protected]/dist/pvotly.global.css" /> <div id="inventory-report" style="height: 440px"></div> <script src="https://unpkg.com/@pvotly/[email protected]/dist/pvotly.global.js"></script> <script> const inventory = [ { warehouse: 'Dallas', category: 'Hardware', quantity: 62 }, { warehouse: 'Dallas', category: 'Accessories', quantity: 119 }, { warehouse: 'Seattle', category: 'Hardware', quantity: 44 }, ]; new Pvotly.PivotTable('#inventory-report', { dataSource: { data: inventory }, slice: { rows: [{ uniqueName: 'warehouse' }], columns: [{ uniqueName: 'category' }], measures: [{ uniqueName: 'quantity', aggregation: 'sum' }], }, }); </script>
Render a React Pivot Table
import { useRef } from 'react';
import { PivotTable, type PivotTableHandle } from '@pvotly/react';
import '@pvotly/web/styles.css';
const supportTickets = [
{ team: 'Billing', status: 'Open', tickets: 18 },
{ team: 'Billing', status: 'Closed', tickets: 62 },
{ team: 'Technical', status: 'Open', tickets: 27 },
{ team: 'Technical', status: 'Closed', tickets: 91 },
];
export default function TicketReport() {
const tableRef = useRef<PivotTableHandle>(null);
return (
<>
<button onClick={() => tableRef.current?.exportTo('csv')}>
Export report
</button>
<PivotTable
ref={tableRef}
height={480}
dataSource={{ data: supportTickets }}
slice={{
rows: [{ uniqueName: 'team' }],
columns: [{ uniqueName: 'status' }],
measures: [{ uniqueName: 'tickets', aggregation: 'sum' }],
}}
onCellDoubleClick={({ records }) => {
console.log(records);
}}
/>
</>
);
}
Parse CSV Data and Group Dates
import { PivotTable } from '@pvotly/web';
import '@pvotly/web/styles.css';
const csvText = `orderedAt,market,revenue
2026-01-12,North,4200
2026-02-07,South,5100
2026-04-18,North,3900`;
new PivotTable('#order-report', {
dataSource: {
csv: csvText,
csvOptions: {
header: true,
dynamicTyping: true,
},
mapping: {
orderedAt: {
type: 'date',
dateParts: ['year', 'quarter'],
},
revenue: {
aggregation: 'sum',
},
},
},
slice: {
rows: [
{ uniqueName: 'orderedAt.year' },
{ uniqueName: 'orderedAt.quarter' },
],
columns: [{ uniqueName: 'market' }],
measures: [{ uniqueName: 'revenue', aggregation: 'sum' }],
},
});
Add a Calculated Measure
const financeReport = {
dataSource: {
data: [
{ department: 'Online', revenue: 14800, cost: 6900 },
{ department: 'Retail', revenue: 12100, cost: 5800 },
],
},
slice: {
rows: [{ uniqueName: 'department' }],
measures: [
{ uniqueName: 'revenue', aggregation: 'sum', caption: 'Revenue' },
{ uniqueName: 'cost', aggregation: 'sum', caption: 'Cost' },
{
uniqueName: 'grossMargin',
caption: 'Gross Margin',
formula: "sum('revenue') - sum('cost')",
},
],
},
};
Supported aggregate functions in formulas include sum, count, distinctCount, average, median, min, max, product, first, last, stdev, stdevp, var, and varp. A formula with invalid syntax throws a SyntaxError.
Apply Number Formats and Conditional Formatting
new PivotTable('#kpi-report', {
dataSource: {
data: [
{ department: 'Support', completionRate: 0.92 },
{ department: 'Sales', completionRate: 0.78 },
],
},
slice: {
rows: [{ uniqueName: 'department' }],
measures: [
{
uniqueName: 'completionRate',
aggregation: 'average',
format: 'percent',
},
],
},
formats: [
{
name: 'percent',
style: 'percent',
minimumFractionDigits: 1,
maximumFractionDigits: 1,
},
],
conditions: [
{
measure: 'completionRate',
condition: { op: '<', value: 0.8 },
format: { color: '#b42318', fontWeight: '700' },
},
],
});
Build a Custom Headless Renderer
import { PivotEngine } from '@pvotly/core';
const engine = new PivotEngine({
dataSource: {
data: [
{ plan: 'Basic', month: 'January', subscribers: 180 },
{ plan: 'Pro', month: 'January', subscribers: 92 },
{ plan: 'Basic', month: 'February', subscribers: 203 },
],
},
slice: {
rows: [{ uniqueName: 'plan' }],
columns: [{ uniqueName: 'month' }],
measures: [{ uniqueName: 'subscribers', aggregation: 'sum' }],
},
});
const grid = engine.getGrid();
for (const row of grid.rowLeaves) {
for (const column of grid.columnLeaves) {
const measure = grid.measures[0];
const cell = grid.getCell(row, column, measure);
console.log(row.caption, column.caption, cell.formatted);
}
}
Configuration Options
Root Report Configuration
dataSource(DataSourceConfig): Defines the records that feed the report. Providedata,matrix, orcsvfor a local source. Addremoteorfetcherfor an async source.slice(Slice): Defines the rows, columns, measures, filters, sorting, and expanded members.options(PivotOptions): Defines grid layout, configurator behavior, formatting defaults, drill-through behavior, and rendering controls.formats(NumberFormat[]): Defines named formats that measures reference throughmeasure.format.conditions(ConditionalFormat[]): Defines ordered conditional formatting rules.localization(Localization): Overrides report labels such as grand totals, blank members, and aggregation captions.valuesAxis('columns' | 'rows'): Places measures on the columns axis or the rows axis. This setting takes precedence overoptions.grid.measurePosition.customAggregators(Record<string, AggregatorDefinition>): Registers report-level aggregation names beside the built-in aggregations.locale(string): Sets a grid-wide BCP-47 locale fallback for Intl number and date formatting.serverSide(ServerSideConfig): Supplies an asyncqueryfunction that returns a ready-to-renderPivotGridfor server-side aggregation.
Data Source Options
data(DataRecord[]): Accepts an array of flat objects.matrix(DataValue[][]): Accepts an array of arrays. The first row supplies field names.csv(string): Accepts raw CSV text.csvOptions.delimiter(string): Sets the CSV separator.csvOptions.header(boolean): Treats the first CSV row as field names. The default istrue.csvOptions.quote(string): Sets the CSV quote character. The default is".csvOptions.trim(boolean): Trims whitespace around parsed values. The default istrue.csvOptions.dynamicTyping(boolean): Coerces numeric, boolean, and date values during parsing. The default istrue.remote(RemoteDataSource): Describes a remotejsonorcsvendpoint. Seturl, then addfetchOptions,transform, orrefreshIntervalwhen required.fetcher(() => Promise<DataRecord[]> | DataRecord[]): Supplies records through a custom async or synchronous function.mapping(FieldMap): Overrides source-field metadata.
Field Mapping Options
caption(string): Replaces the field label in the UI.type('string' | 'number' | 'boolean' | 'date' | 'datetime'): Overrides automatic field-type detection.dateParts(DatePart[]): Expands a date field intoyear,quarter,month,monthName,week,dayOfMonth,weekday,date,hour,minute, orsecond.visible(boolean): Hides the field from the available-field list.isMeasure(boolean): Marks a numeric field as measure-only.aggregation(AggregationName): Sets the default aggregation for a field in Values.format(string): Sets the default named number format.
Slice Options
rows(SliceField[]): Places dimensions on the row axis.columns(SliceField[]): Places dimensions on the column axis.measures(MeasureConfig[]): Defines Values-axis aggregations and formulas.reportFilters(SliceField[]): Filters the whole report and keeps the field off the grid axes.expands(ExpandsConfig): Stores expanded header paths throughexpandAll,rows, andcolumns.drills(DrillsConfig): Stores collapsed header paths throughdrillAll,rows, andcolumns.sorting(SortingConfig): Sorts theroworcolumnaxis from a measure and optional opposite-axis tuple.flatSort(Array<{ uniqueName: string; sort: SortDirection }>): Controls sort order in the flat grid layout.
Slice Field Options
uniqueName(string): Names a source field or date part such asorderedAt.quarter.caption(string): Replaces the field caption for this report placement.sort('asc' | 'desc' | 'unsorted'): Sorts members on the current axis.filter(FieldFilter): Attaches a member, value, label, or query filter.
Measure Options
uniqueName(string): Names a source field or a unique calculated-measure identifier.caption(string): Replaces the measure caption.aggregation(AggregationName): Usessum,count,distinctCount,average,median,min,max,product,first,last,stdev,stdevp,var,varp,none, or a custom aggregation name.showDataAs(ShowDataAs): Appliesraw, percentage, parent percentage, running total, rank, or difference-from-previous transformations.format(string): References a named format.formula(string): Defines a calculated measure. A formula overridesaggregation.grandTotalCaption(string): Replaces the measure caption for its grand total.active(boolean): Controls whether the measure participates in the report.
Filter Options
members(MemberFilter): Usesincludeorexcludearrays for explicit member selection.value(ValueFilter): Uses a measure andtop,bottom,equal,notEqual,greater,greaterEqual,less,lessEqual,between, ornotBetweenrules.label(LabelFilter): Usescontains,notContains,beginsWith,endsWith,equal,notEqual, orregexrules against member captions.query(QueryFilter): Uses field predicates such asmin,max,greater,less,equal,after, andbefore.
Grid and Engine Options
grid.type('compact' | 'classic' | 'flat'): Selects the layout used for nested headers and subtotal lines.grid.showGrandTotals('on' | 'off' | 'rows' | 'columns'): Shows grand totals on both axes or one axis.grid.showTotals('on' | 'off' | 'rows' | 'columns'): Shows subtotals for nested groups.grid.showHeaders(boolean): Shows row and column header titles.grid.showFilter(boolean): Shows field-list filter icons in grid headers.grid.title(string): Sets the empty top-left grid caption.grid.rowLayout('compact' | 'gutter'): Uses an indented row-label column or an outline-style expander gutter.grid.rowLabelsCaption(string): Sets the row-labels caption in gutter layout.grid.measurePosition('columns' | 'rows'): Places measures on columns or rows whenvaluesAxisis not set.grid.width('fill' | 'content'): Stretches the grid to its container or preserves content width.configuratorActive(boolean): Opens or hides the field-list configurator.configuratorButton(boolean): Shows or hides the configurator toolbar button.showAggregationLabels(boolean): Displays aggregation labels with measures.defaultAggregation(AggregationType): Selects the reducer assigned to newly added Values fields.virtualization(boolean): Enables virtualized rendering.drillThrough(boolean): Enables source-record access from a body cell.readOnly(boolean): Hides configuration controls.datePattern(string): Sets the default date display pattern.dateTimePattern(string): Sets the default date-time display pattern.locale(string): Sets the engine-level Intl locale.useWorker(boolean): Requests Web Worker grid computation for async grid building.worker(WorkerConfig): Supplies a workerurlorfactoryforuseWorker.
DOM Widget Options
toolbar(boolean): Shows the toolbar. The default istrue.fieldList(boolean): Shows the drag-and-drop field-list panel. The default istrue.theme('light' | 'dark' | string): Selects a built-in theme or a custom theme name.height(string | number): Sets widget height.width(string | number): Sets widget width.
Number Format Options
name(string): Names the format. Measures reference this value throughformat.decimalPlaces(number): Sets fixed decimal precision.maxDecimalPlaces(number): Sets the maximum fractional precision.decimalSeparator(string): Replaces the decimal separator.thousandsSeparator(string): Replaces the grouping separator. Use an empty string to suppress grouping.currencySymbol(string): Adds a currency or unit prefix or suffix.currencySymbolAlign('left' | 'right'): Sets currency-symbol position.isPercent(boolean): Multiplies the value by 100 and appends%.negativeFormat('minus' | 'parentheses' | 'redMinus'): Formats negative values.nullValue(string): Sets text for blank cells.infinityValue(string): Sets text for invalid numeric results.dateTimePattern(string): Sets a date pattern withyyyy,yy,MM,dd,HH,mm, andss.textAlign('left' | 'center' | 'right'): Aligns formatted cells.intl(boolean): Forces the Intl formatter path.locale(string): Overrides the report-level Intl locale.style('decimal' | 'currency' | 'percent'): Sets the Intl number style.currency(string): Supplies an ISO 4217 code for Intl currency formatting.currencyDisplay('symbol' | 'narrowSymbol' | 'code' | 'name'): Controls the Intl currency label.minimumFractionDigits(number): Sets the Intl minimum fractional precision.maximumFractionDigits(number): Sets the Intl maximum fractional precision.useGrouping(boolean): Toggles Intl grouping separators.dateTimeFormat(Intl.DateTimeFormatOptions): Supplies Intl date and time options.
Conditional Formatting and Localization Options
conditions[].measure(string): Restricts a rule to one measure.conditions[].condition(ConditionExpr): Tests a cell with=,!=,>,>=,<,<=,between,contains,isTrue, orisFalse.conditions[].format(CellStyle): SetsbackgroundColor,color,fontFamily,fontSize,fontWeight,fontStyle, ortextAlign.localization.grandTotal(string): Replaces the grand-total label.localization.grandTotalLabel(string): Supplies an alternate grand-total label.localization.totalLabel(string): Sets the subtotal template.localization.blankMember(string): Replaces blank member labels.localization.noData(string): Replaces the empty-grid message.localization.aggregations(Partial<Record<AggregationType, string>>): Replaces aggregation captions.
API Methods
PivotEngine Methods
import { PivotEngine } from '@pvotly/core';
const engine = new PivotEngine(reportConfig);
// Replace the full data source.
engine.updateData({ data: nextRecords });
// Read the normalized data model and field metadata.
engine.getDataset();
engine.getFields();
engine.getMembers('region');
// Read or replace report configuration.
engine.getConfiguration();
engine.getReport();
engine.setConfiguration(nextReportConfig);
engine.setReport(nextReportConfig);
engine.getSlice();
engine.setSlice(nextSlice);
engine.setOptions({ grid: { type: 'classic' } });
engine.setFormats(nextFormats);
engine.setConditions(nextConditions);
// Move or remove fields across report axes.
engine.setFieldAxis('region', 'rows', 0);
engine.addToRows('region');
engine.addToColumns('product');
engine.addToValues('sales');
engine.addToFilters('year');
engine.removeField('year');
// Replace measures or edit their calculation settings.
engine.setMeasures(nextMeasures);
engine.addCalculatedMeasure({
uniqueName: 'profit',
formula: "sum('revenue') - sum('cost')",
});
engine.setAggregation('sales', 'average');
engine.setMeasureFormat('sales', 'currency');
engine.setShowDataAs('sales', 'percentOfGrandTotal');
// Sort members or an axis by measure values.
engine.sortField('region', 'desc');
engine.sortByValue('rows', {
direction: 'desc',
measure: 'sales',
});
// Apply or clear a field filter.
engine.setFilter('region', {
type: 'members',
include: ['West', 'East'],
});
engine.clearFilter('region');
// Expand or collapse report members.
engine.expand('rows', {
tuple: [{ uniqueName: 'region', value: 'West' }],
});
engine.collapse('rows', {
tuple: [{ uniqueName: 'region', value: 'West' }],
});
engine.expandAll();
engine.collapseAll();
// Read source records or the computed pivot grid.
engine.getRecords();
engine.getGrid();
Dataset and Core Utility Methods
import {
Dataset,
buildGrid,
compareValues,
createAggregator,
formatValue,
formatDate,
resolveFormat,
compileFormula,
resolveCellStyle,
datePartValue,
datePartCaption,
toDate,
parseCsv,
inferFieldType,
normalizeValue,
} from '@pvotly/core';
const dataset = new Dataset({ data: revenueRecords });
// Inspect normalized records, fields, values, and captions.
dataset.parseName('orderedAt.quarter');
dataset.fieldType('sales');
dataset.resolveValue(revenueRecords[0], 'sales');
dataset.memberCaption('region', 'West');
dataset.fieldCaption('sales');
dataset.getMembers('region');
dataset.listFields();
// Build one grid without the stateful engine.
buildGrid(dataset, reportConfig);
// Use core parsing, aggregation, formatting, comparison, and date helpers.
compareValues('East', 'West');
createAggregator('average');
formatValue(1234.5);
formatDate(new Date(), 'yyyy-MM-dd');
resolveFormat({ name: 'currency' });
compileFormula("sum('revenue') - sum('cost')");
resolveCellStyle(8200, 'sales', []);
datePartValue(new Date(), 'quarter');
datePartCaption(2, 'quarter');
toDate('2026-04-15');
parseCsv('region,sales\nWest,1200');
inferFieldType(revenueRecords, 'sales');
normalizeValue('1200');
EventEmitter Methods
// Subscribe and receive an unsubscribe function.
const stop = engine.on('reportChange', (config) => {
console.log(config);
});
// Subscribe for one event only.
engine.once('ready', () => {
console.log('Pivot engine is ready.');
});
// Remove one handler, emit an event, or clear every subscription.
engine.off('reportChange', handler);
engine.emit('reportChange', engine.getConfiguration());
engine.clear();
stop();
PivotTable Methods
import { PivotTable } from '@pvotly/web';
const pivot = new PivotTable('#revenue-report', reportConfig);
// Force a new render or replace the report.
pivot.refresh();
pivot.getConfiguration();
pivot.setConfiguration(nextReportConfig);
// Subscribe or unsubscribe from pivot events.
const unsubscribe = pivot.on('cellClick', ({ cell }) => {
console.log(cell.formatted);
});
pivot.off('cellClick', handler);
// Change display and interaction controls.
pivot.setTheme('dark');
pivot.setThemeTokens({ accent: '#0f766e' });
pivot.toggleFieldList(false);
await pivot.toggleFullscreen();
pivot.closeDialog();
// Export, print, and remove the widget.
pivot.exportTo('excel', { filename: 'quarterly-report', raw: true });
pivot.print('Quarterly Revenue');
pivot.destroy();
unsubscribe();
Export and UI Utility Methods
import {
applyTheme,
setThemeTokens,
gridToMatrix,
exportToCSV,
exportToHTML,
exportToJSON,
exportToExcel,
serializeExport,
downloadExport,
printGrid,
renderGrid,
mountFieldList,
mountToolbar,
openFilterDialog,
openFormatDialog,
openConditionalDialog,
} from '@pvotly/web';
const grid = engine.getGrid();
// Apply a theme or design tokens to a widget root.
applyTheme(document.querySelector('#revenue-report'), 'dark');
setThemeTokens(document.querySelector('#revenue-report'), {
accent: '#0f766e',
});
// Serialize or download a computed grid.
gridToMatrix(grid, true);
exportToCSV(grid, { filename: 'revenue' });
exportToHTML(grid);
exportToJSON(grid);
exportToExcel(grid);
serializeExport(grid, 'csv');
downloadExport(grid, 'csv', { filename: 'revenue' });
printGrid(grid, 'Revenue');
// Build a custom widget composition from pvotly UI parts.
renderGrid(pivotContext, document.querySelector('#grid-host'));
mountFieldList(pivotContext, document.querySelector('#fields-host'));
mountToolbar(pivotContext, document.querySelector('#toolbar-host'));
openFilterDialog(pivotContext, 'region');
openFormatDialog(pivotContext, 'sales');
openConditionalDialog(pivotContext);
React Ref Methods
import { useRef } from 'react';
import { PivotTable, type PivotTableHandle } from '@pvotly/react';
function ReportActions() {
const pivotRef = useRef<PivotTableHandle>(null);
function exportCsv() {
pivotRef.current?.getConfiguration();
pivotRef.current?.setConfiguration(nextReportConfig);
pivotRef.current?.exportTo('csv', { filename: 'report' });
pivotRef.current?.print('Report');
pivotRef.current?.refresh();
}
return <PivotTable ref={pivotRef} dataSource={{ data: revenueRecords }} />;
}
Events
const stopReady = pivot.on('ready', () => {
console.log('The report is ready.');
});
const stopReportChange = pivot.on('reportChange', (config) => {
console.log(config);
});
const stopDataChange = pivot.on('dataChange', ({ records }) => {
console.log(records);
});
const stopCellClick = pivot.on('cellClick', ({ cell }) => {
console.log(cell.formatted);
});
const stopCellDoubleClick = pivot.on('cellDoubleClick', ({ cell, records }) => {
console.log(cell, records);
});
const stopFilterChange = pivot.on('filterChange', ({ field, filter }) => {
console.log(field, filter);
});
const stopSortChange = pivot.on('sortChange', ({ field, direction }) => {
console.log(field, direction);
});
const stopDrillThrough = pivot.on('drillThrough', ({ cell, records }) => {
console.log(cell, records);
});
const stopError = pivot.on('error', ({ message, error }) => {
console.error(message, error);
});
// Run each unsubscribe function when the host view unmounts.
[
stopReady,
stopReportChange,
stopDataChange,
stopCellClick,
stopCellDoubleClick,
stopFilterChange,
stopSortChange,
stopDrillThrough,
stopError,
].forEach((stop) => stop());
Alternatives:
- Advanced Data Grid/Table Library in Vanilla JavaScript – VanillaGrid
- High Performance Table Data Presentation Library – regular-table
- 10 Best Data Table / Data Grid Libraries In JavaScript
FAQs:
Q: Which data formats does pvotly accept?
A: The report accepts object records, an array-of-arrays matrix with a header row, raw CSV text, a remote HTTP source, or a custom fetcher. Use mapping when imported field types or captions need overrides.
Q: Why does the pivot table render without layout styles?
A: Import @pvotly/web/styles.css once in the application entry point. The stylesheet includes base widget styles and the built-in light and dark themes.
Q: How do I save a user’s field order, filters, and selected measures?
A: Call getConfiguration() after the report changes and store the returned serializable object. Pass that object to setConfiguration() when the report loads again.
Q: How should a large report start?
A: Enable options.virtualization and limit high-cardinality dimensions before users expand every hierarchy. Review the rendered row and column counts before adding several measures or deep date levels.
Q: How do I supply data as a CSV string at runtime?
A: Use the csv field in dataSource. The engine will parse it automatically. You can tweak delimiter, header presence, and type coercion with csvOptions.
Q: Does the headless engine work in Node.js or server-side environments?
A: Yes. @pvotly/core has zero dependencies and no DOM access. You can instantiate the engine, provide data, and call getGrid() in a Node process, worker, or test runner.
Q: Why do my date fields not appear as drillable hierarchies?
A: Add a mapping entry for the date field with type: 'date' and the desired dateParts, then reference them in the slice as fieldName.year, fieldName.month, etc.
Q: How do I persist and restore a user’s report layout?
A: Call engine.getConfiguration() to obtain a serializable object. Store it, and later pass it to engine.setConfiguration() or the <PivotTable /> dataSource/slice/options props.







