Export - Grouped PDF + Print
Group the grid and export: the PDF + xlsx carry bold group headers and per-cluster subtotal rows (auto-carried from grouping state). Polished Print is the zero-dependency "Save as PDF" route with a repeated header and title. (requires @svgrid/enterprise)
A live, editable Svelte 5 data grid example from the SvGrid gallery (Data Export & Import). See the SvGrid documentation for the full API.
What this example shows
Group the grid, then export - the PDF and xlsx carry the grouping as bold group headers + subtotal rows (auto-carried from the grid's grouping state, no groupBy needed). The Print button opens the zero-dependency "Save as PDF" view with a repeated header, title, and formatted values.
Imports, features and API used
Imports: @svgrid/grid, @svgrid/enterprise, ../shared/seed
Table features registered: rowSortingFeature, columnGroupingFeature, rowExpandingFeature
Columns: country (Country), company (Company), product (Product), quantity (Qty), price (Price)
SvGridApi methods called: api.setGroupBy()
Source code (202-export-pdf-grouped-and-print.svelte)
<script lang="ts">
/**
* 202. Grouped PDF + polished Print (Enterprise)
* ----------------------------------------------
* Group the grid, then export - the PDF and xlsx carry the grouping as bold
* group headers + subtotal rows (auto-carried from the grid's grouping
* state, no groupBy needed). The Print button opens the zero-dependency
* "Save as PDF" view with a repeated header, title, and formatted values.
*/
import {
SvGrid,
tableFeatures,
rowSortingFeature,
columnGroupingFeature,
rowExpandingFeature,
type ColumnDef,
type SvGridApi,
} from '@svgrid/grid'
import {
installEnterprise,
setLicenseKey,
dismissUnlicensedNudge,
type EnterpriseGridApi,
} from '@svgrid/enterprise'
import { makeOrders, type Order } from '../shared/seed'
setLicenseKey('SVENTERPRISE-DEV-LOCAL')
dismissUnlicensedNudge()
const features = tableFeatures({
rowSortingFeature,
columnGroupingFeature,
rowExpandingFeature,
})
let rows = $state<Order[]>(makeOrders(240))
// When grouping, export ALL rows made CONTIGUOUS by group (so every row shows
// under its header - expanded in the PDF, an outline in Excel - regardless of
// which groups are collapsed on screen). Groups keep their first-seen order
// and within-group order, matching the grid (not an alphabetical sort).
const groupedRows = $derived.by(() => {
if (groupBy.length === 0) return null
const buckets = new Map<string, Order[]>()
for (const r of rows) {
const key = groupBy.map((k) => String((r as unknown as Record<string, unknown>)[k] ?? '')).join('')
const bucket = buckets.get(key)
if (bucket) bucket.push(r)
else buckets.set(key, [r])
}
return [...buckets.values()].flat()
})
let api = $state<EnterpriseGridApi<typeof features, Order> | null>(null)
let groupBy = $state<string[]>(['country'])
let orientation = $state<'portrait' | 'landscape'>('landscape')
let lastAction = $state('')
let errorMsg = $state<string | null>(null)
const columns: ColumnDef<typeof features, Order>[] = [
{ field: 'country', header: 'Country', width: 110 },
{ field: 'company', header: 'Company', width: 150 },
{ field: 'product', header: 'Product', width: 160 },
{
field: 'quantity',
header: 'Qty',
width: 90,
align: 'right',
format: { type: 'number', options: { maximumFractionDigits: 0 } },
},
{
field: 'price',
header: 'Price',
width: 120,
align: 'right',
format: { type: 'currency', currency: 'USD' },
},
]
function onReady(next: SvGridApi<typeof features, Order>) {
api = installEnterprise(next)
api.setGroupBy(groupBy)
}
function applyGroup(by: string[]) {
groupBy = by
api?.setGroupBy(by)
}
async function run(label: string, fn: () => Promise<unknown>) {
if (!api) return
errorMsg = null
try {
await fn()
lastAction = label
} catch (err) {
errorMsg = err instanceof Error ? err.message : String(err)
}
}
// Grouped -> all rows ordered by the group keys; ungrouped -> displayed view.
const groupOpts = () =>
groupBy.length > 0 ? { groupBy, rows: groupedRows ?? undefined } : {}
const exportPdf = () =>
run('PDF', () =>
api!.exportData({
format: 'pdf',
filename: 'orders',
pageOrientation: orientation,
...groupOpts(),
pdf: { title: 'Orders by ' + (groupBy.join(' / ') || 'none'), subtitle: `${rows.length} rows` },
}),
)
const exportXlsx = () =>
run('Excel', () => api!.exportData({ format: 'xlsx', filename: 'orders', ...groupOpts() }))
const print = () =>
run('Print', () =>
api!.print({
title: 'Orders',
subtitle: `Grouped by ${groupBy.join(' / ') || 'none'}`,
...(groupBy.length > 0 && groupedRows ? { rows: groupedRows } : {}),
orientation,
}),
)
</script>
<section class="flex flex-col flex-1 min-h-0 gap-3">
<div class="note text-sm shrink-0">
Group the grid, then export. The <strong>PDF</strong> and <strong>Excel</strong>
files include a group header + a subtotal row (Qty + Price) per cluster.
<strong>Print</strong> opens the browser "Save as PDF" view.
</div>
<div class="flex flex-wrap items-center gap-2 shrink-0">
<span class="note text-xs">Group by</span>
<button class="chip" class:on={groupBy.length === 0} onclick={() => applyGroup([])}>None</button>
<button class="chip" class:on={groupBy.join() === 'country'} onclick={() => applyGroup(['country'])}>Country</button>
<button class="chip" class:on={groupBy.join() === 'country,company'} onclick={() => applyGroup(['country', 'company'])}>Country + Company</button>
<span class="sep mx-1 h-5 w-px"></span>
<label class="note flex items-center gap-1.5 text-xs">
<input type="checkbox" checked={orientation === 'landscape'} onchange={(e) => (orientation = e.currentTarget.checked ? 'landscape' : 'portrait')} class="h-4 w-4" />
Landscape
</label>
<button class="btn" disabled={!api} onclick={exportPdf}>Export PDF</button>
<button class="btn" disabled={!api} onclick={exportXlsx}>Export Excel</button>
<button class="btn btn-primary" disabled={!api} onclick={print}>Print / Save as PDF</button>
{#if lastAction}<span class="text-xs text-green-600 dark:text-green-400">Exported {lastAction}</span>{/if}
{#if errorMsg}<span class="text-xs text-red-600 dark:text-red-400">{errorMsg}</span>{/if}
</div>
<div class="flex-1 min-h-0">
<SvGrid responsive={true}
data={rows}
columns={columns}
features={features}
showGroupingControls={true}
showPagination={false}
rowHeight={34}
containerHeight="100%"
fitColumns={true}
onApiReady={onReady}
/>
</div>
<footer class="note text-xs shrink-0">
Enterprise feature. Grouping is auto-carried into the export - no
<code>groupBy</code> option needed. Currency + number columns get summed
subtotals; the PDF uses our own pdfmake layout (repeated header, page
numbers, theme colors).
</footer>
</section>
<style>
.note { color: var(--sg-muted, rgb(100 116 139)); }
.sep { background: var(--sg-border, rgb(203 213 225)); }
.chip, .btn {
border-radius: var(--sg-radius, 0.375rem);
border: 1px solid var(--sg-border, rgb(203 213 225));
padding: 0.3rem 0.7rem;
font-size: 0.8rem;
font-weight: 500;
background: var(--sg-bg, #fff);
color: var(--sg-fg, rgb(15 23 42));
cursor: pointer;
}
.chip.on {
border-color: var(--sg-accent, rgb(79 70 229));
background: color-mix(in srgb, var(--sg-accent, rgb(79 70 229)) 12%, var(--sg-bg, #fff));
color: var(--sg-accent, rgb(55 48 163));
}
.btn:disabled { opacity: 0.5; }
.btn-primary {
border-color: var(--sg-accent, rgb(79 70 229));
background: var(--sg-accent, rgb(79 70 229));
color: var(--sg-on-accent, #fff);
}
</style>Related articles
- What's New in SvGrid Enterprise - Export, Pivot, Import, Print, and AI - A practical look at @svgrid/enterprise - pivot tables, Excel and PDF export, data import, printing, and an AI assistant. Covers when each feature earns its place and what to watch out for in production.
- Multi-Level (Grouped) Column Headers in SvGrid - Band related columns under a shared parent header using SvGrid's nested column definition - how to nest, pin, combine with sorting and filtering, and when NOT to use grouping.
- Importing CSV into a Svelte Data Grid - Parse a user-uploaded CSV file into objects, map its headers to grid columns, validate rows before committing, and handle the edge cases that actually bite you in production.
More Data Export & Import examples
- Excel / CSV import - File picker + column mapping + per-row validation preview before commit. Reads xlsx / csv / tsv / json with format auto-detect.
- Export + Print - Enterprise feature pack: download to Excel, PDF, CSV, TSV, HTML, or open a printable view in a new window.
- Export - Theme-matched - One xlsx, light or dark - styles read from the same --sg-* tokens the grid renders with.
- Export - Header + Footer + Logo - Branded xlsx: PNG logo + title + subtitle in the page header, generated date + page numbers in the footer.
- Export - Cell images - Product grid with thumbnail column. On xlsx export each thumbnail is embedded as a real picture cell.