Active Foundation

Search documentation

Search all pages, headings and text, in the current package or topic or in all documentation.

Export

CSV, JSON, Excel, PDF and clipboard export, data and presentation output, and the optional dependencies.

@workspace/active-grid 11.0.0Status unknowncontent differs from the recorded release 11.0.0baseline ce081dc3

The package's own guide

This guide is the package's docs/guides/export.md, included unchanged from the package as it is in Foundation. Links to the package's internal design documents keep their text.

What each format guarantees per script, and which route to take when the grid does not: export-guarantees.md.

Active Grid supports exporting data to CSV, JSON, Excel (.xlsx), PDF and clipboard (TSV). For API surface see ../api/cells-utils.md.

Output classes: data, presentation, clipboard#

Every export surface belongs to exactly one output class (WS-04, PP-013/PP-014). This is the core contract of the export API:

SurfaceClassSelected how
CSV / JSON / XLSXDATA defaultoptions omitted or {mode: 'data'}
CSV / JSON / XLSXPRESENTATIONexplicit {mode: 'presentation', locale}
PDFPRESENTATIONalways — PDF is human output by definition
Clipboard (TSV)PRESENTATIONalways — you copy what you see

DATA mode (the default)#

DATA export serializes machine/raw semantic values for downstream processing: importing elsewhere, analysis, durable data transfer. Its output is byte-stable under every viewer language — no user or admin presentation setting may ever change it.

  • Headers and JSON keys are column ids (field keys), not translated header labels.

  • valueFormatter is never consulted. The value comes from the raw cell value (via the column accessor), or from an explicit meta.exportValue callback when the raw value is not the machine value.

  • Numbers stay native numbers (JSON/XLSX) or serialize with .-decimal and no grouping (CSV). Booleans stay booleans.

  • A declared calendar-day column (type: 'date' / meta.semanticType: 'date') exports the machine form YYYY-MM-DD; a declared instant column (type: 'dateTime') exports ISO 8601 UTC. An undeclared raw Date falls back to the same heuristic the chart CSV export uses: UTC-midnight → calendar day, anything else → full ISO instant.

  • A non-empty value that is not readable as its declared semantic fails the export with a RangeError (the JavaScript built-in) instead of exporting something else: an unreadable date cell (active-i18n D17 amendment) just as an unreadable dateTime cell. Absence still exports empty.

  • A currency column exports its amount as a bare number plus its ISO 4217 identity in a companion column — see below. Never a symbol.

  • A percent column exports the value in its declared stored unit (percentSource): a fraction column exports 0.125, a percentage-points column exports 12.5. No silent factor-100 normalization in either direction.

  • An absent value becomes an empty CSV field, JSON null, or a skipped XLSX cell. Absent is what the cell orchestration uses: null, undefined, '' and a non-finite number (NaN, Infinity, -Infinity; TD-28 and active-i18n F3b-9).

  • Strings export as-is — an identifier like "00123" stays text and keeps its leading zeros.

PRESENTATION mode (explicit)#

Presentation export renders the human-readable form, mirroring the grid's own cell orchestration: badge labels from filterOptions, valueFormatter output for rows that stand for a record (an aggregate uses the column's declared semantics, DC-1), currency/number/percent formatting through the same semantic helpers the cells use, translated header labels as headers/keys. It requires an explicit locale — a locale code or a presentation-context object (GridPresentation, WS-07), forwarded untouched to the central formatters — the grid never derives presentation from anything it resolves itself. Two deliberate differences from the screen: absent cells export as an empty string (an export file has no t() for the localized placeholder), and boolean cells export true/false. Everything else follows the same path the cell takes, which includes the non-finite numbers: NaN and, since active-i18n F3b-9, ±Infinity export as empty, like the placeholder they draw on screen.

typescript
import {exportToCSV} from '@workspace/active-grid';
// DATA (default): machine semanticsexportToCSV(rows, grid, 'users.csv');
// PRESENTATION (explicit): what the user seesexportToCSV(rows, grid, 'users.csv', {mode: 'presentation', locale: 'nl'});

The built-in context menu exports CSV/XLSX/JSON in DATA mode and PDF in presentation mode.

Clipboard is a presentation copy, not a data contract#

copyToClipboard (the menu's row copies and Ctrl+C on selected rows) copies what the screen shows: formatted dates, amounts with their symbol, translated badge labels. That is deliberate — a copy travels to an email, a chat message or a spreadsheet the user is looking at, and should match what they selected visually. It is not the machine-export contract; a consumer that needs data uses the CSV/XLSX/JSON export in the default DATA mode.

The same holds for the other two keyboard copies: the cell-range copy (Ctrl+C on a cell selection) and the single-cell copy (Ctrl+C on the active cell with nothing selected). All three copy routes write the presentation text through one clipboard TSV boundary: a value a spreadsheet would run as a formula gets a leading apostrophe, and a value containing a tab, line break or quote is quoted. A range copy takes the rectangle that encloses every range, leaves cells outside the ranges empty, leaves out the grid's own columns and writes no header row; the context menu's "Copy selection" writes the same block, and "Copy selection with headers" adds one header row. Every copy, keyboard or menu, reports its outcome once through meta.onCopySuccess / meta.onCopyError.

Currency identity in DATA export#

A currency column (meta.semanticType: 'currency') never exports a symbol. The amount exports as a bare number and the ISO 4217 identity travels as data (PP-005/PP-006):

  • The exporter resolves the identity per row from meta.currencyKey (row field) or meta.currencyCode (fixed contract) — never from a locale.

  • A companion column <columnId>_currency is added right after the amount column, carrying the resolved code per row ("USD", "JPY"). In JSON it is a companion property with the same name.

  • The companion is skipped when the currencyKey field is itself a visible exported column — the identity is then already in the export.

  • A cell whose identity cannot be resolved exports an empty companion field: no identity means no identity claim.

  • Group/footer aggregates carry the companion only when every aggregated row shares one currency (client grouping) or when the column has a fixed currencyCode (server grouping, where the client cannot prove the shared currency of a server-wide sum). A mixed-currency aggregate exports as a bare number with an empty companion (PP-010).

Export Formats#

CSV#

typescript
import {exportToCSV} from '@workspace/active-grid';
exportToCSV(rows, grid, 'users.csv'); // DATA mode
  • Comma-separated values by default. Every field is wrapped in double quotes — including the header row, so a header containing the separator cannot produce more columns than the data rows carry.

  • Internal quotes are escaped as "" (RFC 4180). Embedded newlines are kept inside the quoted field.

  • DATA mode: headers are column ids; machine values as described above.

Choosing the separator#

The separator comes from GridConfig.export.csvSeparator and applies to every CSV export in that boundary's tree, headers and data rows alike:

TSX
<GridDefaults export={{csvSeparator: ';'}}>  <App /></GridDefaults>

CsvDelimiter is exactly one of , ; | — a positive list, closed by the type and re-checked at runtime, since a provider config can come from plain JavaScript. Anything else is refused (never truncated or trimmed): the export falls back to a comma and a console.warn in development says so. Two grids under two providers export independently.

Quoting does not depend on the separator. Every field is quoted unconditionally, so a value containing the separator, a quote or a newline is safe whichever separator is chosen, and the formula-injection guard below is unaffected.

The clipboard is not covered by this setting. A clipboard copy is TSV and joins with a tab: that is what Excel expects on paste, while csvSeparator is about a file. The two paths deliberately share no configuration.

  • Presentation mode: headers are the column header labels; display values.

  • Values that a spreadsheet would execute are neutralized — see Formula injection.

JSON#

typescript
import {exportToJSON} from '@workspace/active-grid';
exportToJSON(rows, grid, 'users.json'); // DATA mode
  • Pretty-printed JSON with 2-space indentation; array of objects, one per row.

  • DATA mode: keys are column ids; numbers stay numbers, booleans stay booleans, null stays null; dates are machine strings; currency amounts carry a <id>_currency companion property.

  • Presentation mode: keys are header labels; values are display strings.

Excel (.xlsx)#

typescript
import {exportToExcel} from '@workspace/active-grid';
await exportToExcel(rows, grid, 'users.xlsx'); // DATA mode

Uses the xlsx library (dynamically imported — not in the initial bundle) and writes typed cells in DATA mode (PP-014: XLSX is more than CSV with another extension):

Semantic typeStored cellDisplay format
textstring cell—
numbernumeric cell—
currency amountnumeric cell (+ identity companion)—
percent, fractionnumeric cell with the fraction (0.125)native % format
percent, pointsnumeric cell with the points (12.5)none — no % format
date (calendar)typed date cell (serial)yyyy-mm-dd
dateTimestring cell with ISO 8601 UTC—
booleanboolean cell—
null/undefinedcell skipped—

Two measured decisions worth knowing:

  • Calendar days cannot shift by timezone. The date serial is computed from the YYYY-MM-DD machine form directly (epoch 1899-12-30), never by handing a JS Date to the sheet writer — that path converts through the local timezone and moves a date-only value to the previous day in western zones.

  • Instants stay ISO strings. An Excel date serial carries no timezone, so typing an instant as a date cell would bake the exporting machine's zone into the data. The stable ISO 8601 UTC string is the machine contract (PP-013).

  • The fraction % format changes display only. The stored value remains the fraction; Excel's % format shows 12,5% for 0.125 without altering the semantic value. Percentage-points deliberately get no % format — that would show 12.5 as 1250%.

Presentation mode writes display strings as string cells.

  • The .xlsx extension is added automatically if not provided.

Sheet direction#

typescript
await exportToExcel(rows, grid, 'users.xlsx', {direction: 'rtl'});

direction: 'rtl' marks the workbook's first view right to left (Workbook.Views[0].RTL, in the file rightToLeft="1" on the sheet view): the spreadsheet application puts column A on the right. Nothing else changes. The column order of the data stays logical (column A is still the first column), the cell values keep their logical Unicode order without direction controls, and numbers and dates keep their types. ltr, the default, writes no view. The row menu passes the grid's resolved direction (ADR-054).

No fonts, no styles. The workbook carries data and one view setting. The writer (SheetJS Community Edition) writes no cell styles, and a workbook's look belongs to the spreadsheet application, so GridExcelExportOptions has no font field and GridExportFonts never reaches it. A branded workbook would be a styles-capable backend, which is a decision of its own.

PDF#

typescript
import {exportToPDF} from '@workspace/active-grid';
await exportToPDF(rows, grid, 'users.pdf', {locale: 'nl'});await exportToPDF(rows, grid, 'users.pdf', {  locale: 'nl',  fonts,  direction: 'rtl',});

PDF is human output by definition: it always renders the presentation form (translated headers, formatted values) and therefore takes a required locale. direction: 'rtl' puts the first column on the right of the page, the reading order of an rtl grid; the cell text keeps its logical order.

PDF fonts and refusals#

The PDF backend (jsPDF with jspdf-autotable) draws text without shaping and without bidi, and encodes UTF-16 code units. What it cannot draw correctly the grid refuses with a coded GridExportError; it never emits broken glyphs.

The host owns the fonts. A GridExportFonts names families in fallback order; each face's source (a GridExportFontSource) is the font's bytes or a lazy loader that resolves to them, with an optional weight (1 to 1000, default 400) and style ('normal' or 'italic'). Each family needs a regular face (weight below 600, style normal). The grid takes no URL, fetches nothing, keeps no registry and sets no timeout: the loader is the host's, with its own network rules.

typescript
const fonts: GridExportFonts = {  families: [    {      family: 'Corporate Sans',      faces: [        {source: () => loadFont('/fonts/corporate-regular.ttf')},        {source: () => loadFont('/fonts/corporate-bold.ttf'), weight: 700},      ],    },    {      family: 'Noto Sans CJK',      faces: [{source: () => loadFont('/fonts/noto-cjk.ttf')}],    },  ],};

The same object is an active-charts ChartExportFonts: the two packages own parallel types with one shape (the grid's lacks embedInSvg, because the grid writes no SVG).

Which set applies, highest first:

  1. the call's fonts (exportToPDF*);

  2. the grid's own export.fonts (row menu);

  3. GridDefaults' export.fonts (row menu);

  4. none.

'inherit' on the call or on the grid means "no set from here down". A PDF cannot inherit the page's web fonts (the browser never hands them over as TrueType), so it then draws in Helvetica. A standalone call reads no GridDefaults; the row menu reads the set at the click.

What each cell draws in. Every text unit (a header cell, a data cell, a group label, a group-footer cell) is checked before the document is built:

  1. The guard. Text a backend without shaping cannot draw is refused, whatever the fonts: Arabic, Hebrew, Syriac, Thaana, N'Ko, Mongolian, Tibetan, Thai, Lao, Khmer, Myanmar and the Indic/Brahmic scripts; a combining mark in the drawn text; ZWJ, ZWNJ and variation selectors; emoji; any code point beyond the Basic Multilingual Plane. 'EXPORT_TEXT_UNSUPPORTED', with reason and, for a script, script.

  2. Without a set, the text must be WinAnsi (Latin-1 plus the Windows-1252 additions such as €), drawn in Helvetica. Anything else is 'EXPORT_FONT_REQUIRED'.

  3. With a set, the unit is drawn in the first family whose face covers every code point of it, read from jsPDF's own parse of the font. A unit no family covers is 'EXPORT_GLYPH_COVERAGE' (with the families tried). One cell is never split across families: a cell mixing scripts needs one family that covers all of it.

The header row and the group rows are bold: they ask for the family's bold face. A missing face falls back deterministically (exact, then the same style in the other weight, then the same weight in the other style, then regular) and is never synthesised. Only TrueType faces embed ('EXPORT_FONT_FORMAT_UNSUPPORTED' names what other bytes are). Faces are loaded before the document is built; concurrent exports share one load of a face object, and a failed load is retried by the next export.

The same pipeline serves exportToPDF, exportToPDFGrouped and exportToPDFServerGrouped; the server-grouped route loads its backend and fonts before it asks the server for a single group.

Shaped, selectable PDF for complex scripts is not a package promise: it is a host route (for example browser print). CSV, JSON and XLSX carry every script losslessly.

Export errors#

Every refusal and every missing backend is a GridExportError: code is stable, the message is prose. Its metadata never holds exported text, because a cell can hold personal data and hosts log these errors: at most one code point, a script name, the host's family name, what a font's bytes are, the module that is missing, the families tried. A low-level failure (a host loader's rejection, jsPDF's parser) is the cause.

codeWhenMetadata
'EXPORT_DEPENDENCY_MISSING'xlsx, jspdf or jspdf-autotable could not be loadedformat, missingModule, cause
'EXPORT_FONT_REQUIRED'PDF text outside WinAnsi and no setformat, codePoint
'EXPORT_FONT_FORMAT_UNSUPPORTED'a face is not TrueTypeformat, family, detected
'EXPORT_FONT_FACE_MISSING'the set has no family, or a family has no regular faceformat, family
'EXPORT_FONT_SOURCE_INVALID'empty or non-byte source, unreadable bytes, bad weight or styleformat, family, cause
'EXPORT_FONT_LOAD_FAILED'a host loader rejectedformat, family, cause
'EXPORT_TEXT_UNSUPPORTED'the PDF guard refused a textformat, reason, script, codePoint, normalizable
'EXPORT_GLYPH_COVERAGE'no family covers a whole cellformat, codePoint, families

The codes are active-charts' ChartExportError codes where the meaning is the same. A refusal produces no file: a programmatic call rejects, and a row-menu export reports it to onExportError.

Optional dependencies#

xlsx, jspdf and jspdf-autotable are optional dependencies of the grid. Rendering and importing the package never load them; the XLSX and PDF exporters load them on first use. An installation without one still renders every grid, and the export that needs the missing module fails with 'EXPORT_DEPENDENCY_MISSING' naming it. There is no fallback. A host that offers no XLSX or PDF export leaves them out and removes the format from export.formats. A bundler still resolves the dynamic import at build time, and Vite/Rollup refuses the build when the module is absent (measured in the portable-runtime P9 clean-room consumer). A host that leaves them out declares them external (build.rollupOptions.external); the browser then fails the import at export time and the grid reports 'EXPORT_DEPENDENCY_MISSING'.

Clipboard Copy#

typescript
import {copyToClipboard} from '@workspace/active-grid';
const success = await copyToClipboard(rows, grid, {  locale: 'nl',  includeHeaders: false,});
  • Tab-separated values (TSV format) in the presentation form — see the output-class contract above.

  • Pasteable into Excel, Google Sheets, or any spreadsheet.

  • includeHeaders (default: false) adds a header row with the visible header labels.

  • Returns true on success, false on failure.

  • Uses the navigator.clipboard.writeText API.

  • Minimal quoting. Only a value containing a tab, a newline or a " is wrapped in quotes; plain values stay plain, so pasting into a text editor gives clean output. Without this, a single cell containing a tab shifted every following column of that row. The quoting is lossless: a quoted tab or newline arrives inside its own cell.

  • Values that a spreadsheet would execute are neutralized — see Formula injection.

Formula injection#

A spreadsheet executes a cell whose text starts with certain characters. A CSV or clipboard export therefore neutralizes any value that a spreadsheet would read as something other than the literal text it contains, by prefixing a single apostrophe. This is always on and has no opt-out.

It applies to CSV files and clipboard copy, and covers data cells, column headers, group labels and group-footer aggregates alike — the boundary is defined by where the output goes, not by where the value came from.

Text
=1+1                    ->  '=1+1=cmd|' /C calc'!A0      ->  '=cmd|' /C calc'!A0-Notitie over de klant  ->  '-Notitie over de klant     (would render #NAME?)+Notitie over de klant  ->  '+Notitie over de klant-1+1                    ->  '-1+1                       (would evaluate to 0)@SUM(1,1)               ->  '@SUM(1,1)

The apostrophe stays visible in the cell. That is deliberate: it reads as a marker, where a leading space would read as a defect.

What it deliberately leaves alone#

Your amounts are not affected. The rule tests behaviour, not a list of characters, so machine numbers (-1234.5) and the output of formatNumber and the percent formatters pass through untouched in every supported locale — a negative amount is read by the spreadsheet as a negative number, not as a formula:

Text
-1234.5     (machine form)    unchanged-1.234,5    (nl-NL, de-DE)    unchanged-1,234.5    (en-US)           unchanged-1 234,5    (fr-FR)           unchanged-50%  /  -50 %                unchanged-42                           unchanged

Free text that merely starts with a sign is also left alone when it cannot form an expression — -50% korting afgesproken, -2 (zie bijlage), and a lone - used as a "no value" placeholder all export unchanged.

A fixed list of dangerous prefixes was rejected precisely because it would have prefixed every negative amount in the grid.

Not applied to XLSX, JSON or PDF#

XLSX needs no neutralization: the writer emits typed cells, so a string value starting with = is stored as text and is never evaluated. JSON and PDF have their own correctness rules, where an injected apostrophe would be wrong.

Known limits#

  • Measured on Excel (Office 16, nl-NL) for the CSV and paste routes. LibreOffice Calc and Google Sheets were not measured. @ does not evaluate on Office 16 and is neutralized as hardening for engines that may.

  • A leading + on a value that reads as a number is stripped by Excel itself on paste (+1234 becomes 1234). That is number parsing, not formula execution, so the sanitizer does not touch it. Relevant only if a column ever carries a value with a meaningful plus, such as a phone country code.

What Gets Exported#

Included#

  • All visible columns (in their current order), plus the currency companion columns in DATA mode.

  • The rows of the current grid page (filtered, sorted, within pageSize) -- see Export scope. A programmatic caller passes whatever rows it wants.

  • DATA mode: raw semantic values. Presentation mode: display values.

Excluded#

  • System columns: selection, expander, actions — automatically excluded.

  • Hidden columns: only visible columns are exported.

  • Value formatters in DATA mode: the machine value is exported, never the formatted display string. Use meta.exportValue when the raw value is not the machine value.

Export precedence (DATA mode)#

For each cell, the exporter resolves the machine value in this order:

  1. meta.exportValue(value, row) — explicit machine value, when provided;

  2. the raw cell value via the column accessor;

  3. serialization per declared meta.semanticType (currency/number/percent/date/dateTime);

  4. serialization by runtime type (number, boolean, Date heuristic, text).

Rendered React content and valueFormatter output never participate.

Export scope (context menu)#

The row menu exports what the reader has in front of them, and nothing else. There are two scopes, and both mean the same thing in every mode:

OptionRows exported
Export selection (N)Only the rows that are actually selected locally
Export view (N)The rows of the CURRENT paginated grid page
Export loaded rows (N)Infinite scroll only: the blocks loaded so far

Export view is the current page: after filtering, after sorting, within the current pageIndex and pageSize, in the order the screen shows. pageSize therefore bounds the export -- page size 10 exports at most 10 rows, and the last page exports only what actually remains. The meaning is identical in mode="client" and mode="server", because both read the same paginated row model (grid.getRowModel()). A grid whose pagination control is hidden (pagination={{enabled: false}}) still renders one page-sized slice, and the export follows that slice.

The count next to the label and the number of data rows in the file are the same array. The menu resolves its label and its rows in one step and then counts the very array it writes, so the two cannot drift apart.

Infinite scroll has no page index, so it carries its own label over the rows already fetched. It never fetches missing blocks to satisfy an export.

Outcome: completion and failure#

A menu export is awaited by nobody, so its outcome reaches the host through two callbacks on the export prop (ADR-046):

TSX
<ActiveGrid  // ...  export={{    formats: ['xlsx', 'csv'],    onExportComplete: (format, scope) => notifyDone(format, scope),    onExportError: (format, error, scope) => notifyFailed(format, error),  }}/>
CallbackWhen
onExportComplete(format, scope)the file was handed to the browser
onExportError(format, error, scope)no file was produced; error is the cause

Never both, never the wrong one. scope is a GridExportScope: 'page', 'loaded' or 'selection', the row set the file holds. Without onExportError a failure goes to the developer log; it is never swallowed. The names and first arguments are those of the chart's onExportComplete / onExportError, and the two packages use the same words for data export, presentation export, completion, failure and scope (ADR-046 § 6).

A refusal of the grid itself (a PDF text it cannot draw, a font set it cannot use, a missing optional backend) arrives in onExportError as a GridExportError with its code (Export errors).

A programmatic call needs no callback: a synchronous function (exportToCSV, exportToJSON) throws, an asynchronous one rejects, and a return means the file was handed to the browser.

No cancellation. No export path is cancellable; the internal AbortController of the *ServerGrouped functions is never aborted. Cancellation belongs to a full-dataset export, which is a separate product feature (below).

There is no generic "Export all"#

It was removed on 2026-08-31, not renamed. Two measured reasons:

  • Export view used to read getFilteredRowModel(). In client mode that is every filtered row across ALL pages -- Export view (152) beside a paginator reading 1 - 10 of 152 -- and in server mode it is only the current page, because manualFiltering falls that model back onto the page data. One label, two meanings.

  • Export all promised "all rows, ignoring filters" and, measured on a server-mode grid of fifteen users with a filter active, produced a file with the seven FILTERED rows while the label read Export all (7).

A full-dataset export is a separate product feature: it needs its own permission, accountable-read evidence, volume ceiling, progress, cancellation and error handling. None of that belongs behind a row menu, and the grid does not own the rows it would have to promise.

GridExportRowScope#

typescript
type GridExportRowScope = 'page' | 'loaded';

Which row set the built-in row menu can honestly offer. page is the current paginated page and means the same in client and server mode; loaded belongs to infinite scroll, where no page exists and the model carries every block fetched so far. The grid derives it from pagination.mode and declares it on meta.exportConfig.rowScope; a consumer never sets it.

There is deliberately no value for "the whole dataset": the grid does not own those rows, and a label promising them would be untrue.

Programmatic Export#

The export functions accept any array of Row<TData> objects and a Grid<TData> instance:

typescript
import {exportToCSV, exportToExcel} from '@workspace/active-grid';
// Export all filtered rows (DATA mode)const filteredRows = grid.getFilteredRowModel().rows;exportToCSV(filteredRows, grid, 'filtered-data.csv');
// Export selected rows as the user sees themconst selectedRows = grid.getSelectedRowModel().rows;await exportToExcel(selectedRows, grid, 'selected-data.xlsx', {  mode: 'presentation',  locale: 'nl',});

File Download Mechanism#

All export functions create a Blob, generate a temporary object URL, and trigger a download via a dynamically created <a> element:

typescript
const blob = new Blob([content], {type: mimeType});const url = URL.createObjectURL(blob);const link = document.createElement('a');link.href = url;link.download = filename;link.click();URL.revokeObjectURL(url);

This works in all modern browsers without requiring a server endpoint.

Grouped Exports#

For grouped data (client-side or server-side), the package exports parallel functions that preserve group structure in the output:

typescript
import {  exportToCSVGrouped,  exportToJSONGrouped,  exportToExcelGrouped,  exportToCSVServerGrouped,  exportToJSONServerGrouped,  exportToExcelServerGrouped,  type ServerGroupedExportConfig,} from '@workspace/active-grid';

These are programmatic only: the row context menu does not use them. A grouped export writes the whole group tree, which is not one page, so wiring it into the menu would put the label, the count and the file back out of step. The menu always exports the current page's data rows; group header rows are not data and are excluded from the count and from the file.

  • *Grouped variants apply to mode="client" grids with client-side grouping.

  • *ServerGrouped variants take a ServerGroupedExportConfig parameter for server-side grouped pagination, which the caller assembles itself.

  • Group footers export raw aggregate values in DATA mode; currency aggregates follow the shared-currency rule above. Presentation mode formats aggregates like the on-screen group footer.

  • Indentation of nested groups is presentation: DATA mode emits unindented cells (an indented number is no longer a number) and conveys the structure through the group-header and group-footer rows; the grouped JSON is hierarchical in both modes.

Standalone boundary#

The exporter reasons exclusively from generic column semantics (semanticType, currencyKey/currencyCode, percentSource, meta.exportValue) and from the locale the caller supplies for presentation output. active-grid never resolves tenant, administration or application settings itself — any application-specific presentation context enters through the grid's existing configuration surface (columnTypes, locale, explicit export options).

This page as Markdown