# Export + Print

Enterprise feature pack: download to Excel, PDF, CSV, TSV, HTML, or open a printable view in a new window.

Requires `@svgrid/enterprise` (and `@svgrid/grid`). Category: Data Export & Import. Live demo: https://svgrid.com/demos/21-export-and-print/

Install: `npm i @svgrid/grid @svgrid/enterprise` (Svelte 5).

## About this example

The export feature pack of the Svelte 5 data grid: download the visible grid to Excel, PDF, CSV, TSV or HTML, or open a printable view in a new window. The grid is plain @svgrid/grid; installEnterprise(api) adds api.exportData(...) and api.print(...) onto the same SvGridApi you already hold, so the export buttons are a few lines each and respect the current sort, filters and row selection.

Demonstrates the @svgrid/enterprise feature pack: download the visible grid to Excel, PDF, CSV, TSV, or HTML, and open a printable view in a new window.

The grid itself is plain @svgrid/grid. Pro is installed via installEnterprise(api) which adds api.exportData(...) and api.print(...) onto the same SvGridApi object you already have.

## Imports, features and API used

- Imports: `@svgrid/grid`, `@svgrid/enterprise`, `../shared/seed`
- Table features registered: `rowSortingFeature`, `columnFilteringFeature`, `rowSelectionFeature`
- Columns: `company` (Company), `product` (Product), `sellDate` (Sell date), `quantity` (Quantity), `orderId` (Order ID), `country` (Country), `price` (Price), `company` (Company), `product` (Product), `sellDate` (Sell date), `quantity` (Quantity), `orderId` (Order ID)
- SvGridApi methods called: `api.exportData()`, `api.print()`

## Frequently asked questions

### How do I add export to a grid?

In onApiReady call installEnterprise(api) from @svgrid/enterprise and keep the returned api. Then api.exportData({ format: 'xlsx' }) downloads the file; 'pdf', 'csv', 'tsv' and 'html' are the other formats.

### Is CSV export free?

Yes. api.exportCsv and exportTsv are in the MIT @svgrid/grid package and export values as shown on screen. Excel, PDF and the styled formats are the enterprise part.

### What does print do?

api.print() opens a new window with the grid rendered as a plain table with a title and formatted values and calls the browser's print dialog, which is also the route to Save as PDF without a library.

## Related documentation

- [AI assistant](https://svgrid.com/docs/help/ai.md)
- [Export: a report to Excel and PDF](https://svgrid.com/docs/help/export/report.md)
- [Data export and printing](https://svgrid.com/docs/help/export.md)
- [Choosing a grid testing strategy](https://svgrid.com/docs/recipes/testing-your-grid.md)

## Related articles

- [What's New in SvGrid Enterprise - Export, Pivot, Import, Print, and AI](https://svgrid.com/blog/whats-new-svgrid-enterprise/) - 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.
- [Export a Svelte Data Grid to Excel, CSV, and PDF](https://svgrid.com/blog/export-to-excel-csv-pdf/) - Wire up XLSX, CSV, and PDF export in SvGrid with @svgrid/enterprise - respecting active filters, sort order, and row selection.

## Source code (21-export-and-print.svelte)

```svelte
<script lang="ts">
  /**
   * 21. Export + Print (Pro)
   * ------------------------
   * Demonstrates the @svgrid/enterprise feature pack: download the visible grid to
   * Excel, PDF, CSV, TSV, or HTML, and open a printable view in a new window.
   *
   * The grid itself is plain @svgrid/grid. Pro is installed via
   * installEnterprise(api) which adds api.exportData(...) and api.print(...) onto
   * the same SvGridApi object you already have.
   */
  import {
    SvGrid,
    tableFeatures,
    rowSortingFeature,
    columnFilteringFeature,
    rowSelectionFeature,
    type GridColumns,
    type SvGridApi,
  } from '@svgrid/grid'
  import {
    installEnterprise,
    setLicenseKey,
    getLicenseKey,
    clearLicenseKey,
    dismissUnlicensedNudge,
    type EnterpriseGridApi,
  } from '@svgrid/enterprise'
  import { makeOrders, type Order } from '../shared/seed'

  // The license key is set once at app startup (main.ts), never in a
  // component. This demo borrows whatever key the app set so the toggle can
  // show the unlicensed state (watermark + console nudge) and back. With no
  // key set there is nothing to toggle: the watermark simply shows.
  const appKey = getLicenseKey()
  let licensed = $state(appKey !== null)
  $effect(() => {
    if (!appKey) return
    if (licensed) {
      setLicenseKey(appKey)
      dismissUnlicensedNudge() // clear the watermark the unlicensed view left
    } else {
      clearLicenseKey()
    }
    // Leaving the demo restores the key, so the next page is not unlicensed.
    return () => {
      setLicenseKey(appKey)
      dismissUnlicensedNudge()
    }
  })

  const features = tableFeatures({
    rowSortingFeature,
    columnFilteringFeature,
    rowSelectionFeature,
  })

  let rows = $state<Order[]>(makeOrders(120))
  let api = $state<EnterpriseGridApi<typeof features, Order> | null>(null)
  let lastAction = $state<string>('')
  let busy = $state<string | null>(null)
  let errorMsg = $state<string | null>(null)

  const columns: GridColumns<Order> = [
    { field: 'company',  header: 'Company',  width: 140 },
    { field: 'product',  header: 'Product',  width: 170 },
    { field: 'sellDate', header: 'Sell date', width: 110,
      format: { type: 'date', pattern: 'y-m-d' } },
    { field: 'quantity', header: 'Quantity', width: 90,
      format: { type: 'number', options: { maximumFractionDigits: 0 } } },
    { field: 'orderId',  header: 'Order ID', width: 130 },
    { field: 'country',  header: 'Country',  width: 90 },
    { field: 'price',    header: 'Price',    width: 110,
      format: { type: 'currency', currency: 'USD' } },
  ]

  // Listed explicitly (rather than .map'd from `columns`) so the export
  // module's ExportColumn type (field: string, required) is satisfied
  // without casts - ColumnDef.field is keyof TData | undefined.
  const exportColumns = [
    { field: 'company',  header: 'Company' },
    { field: 'product',  header: 'Product' },
    { field: 'sellDate', header: 'Sell date' },
    { field: 'quantity', header: 'Quantity' },
    { field: 'orderId',  header: 'Order ID' },
    { field: 'country',  header: 'Country' },
    { field: 'price',    header: 'Price' },
  ]

  function onReady(next: SvGridApi<typeof features, Order>) {
    api = installEnterprise(next)
  }

  async function run(label: string, fn: () => Promise<unknown>) {
    if (!api) return
    busy = label
    errorMsg = null
    try {
      await fn()
      lastAction = label
    } catch (err) {
      errorMsg = err instanceof Error ? err.message : String(err)
    } finally {
      busy = null
    }
  }

  const exportFormats: Array<{ label: string; format: 'xlsx' | 'pdf' | 'csv' | 'tsv' | 'html' }> = [
    { label: 'Excel (xlsx)', format: 'xlsx' },
    { label: 'PDF',          format: 'pdf' },
    { label: 'CSV',          format: 'csv' },
    { label: 'TSV',          format: 'tsv' },
    { label: 'HTML',         format: 'html' },
  ]
</script>

<section class="flex flex-col flex-1 min-h-0 gap-3">
  <div class="text-sm shrink-0 xp-note">
    {rows.length} rows. Apply sort or filter - exports always reflect the
    <em>currently displayed</em> rows. Print opens a new window with the
    same view, ready for the browser print dialog.
  </div>

  <div class="flex flex-wrap items-center gap-2 shrink-0">
    {#each exportFormats as f (f.format)}
      <button
        type="button"
        class="rounded border px-3 py-1.5 text-sm font-medium disabled:opacity-50 xp-btn"
        disabled={busy !== null || api === null}
        onclick={() =>
          run(`Exported ${f.label}`, () =>
            api!.exportData({
              format: f.format,
              filename: `orders.${f.format}`,
              columns: exportColumns,
              pageOrientation: 'landscape',
            }),
          )}
      >
        Export {f.label}
      </button>
    {/each}
    <button
      type="button"
      class="rounded border px-3 py-1.5 text-sm font-medium disabled:opacity-50 xp-btn xp-btn-primary"
      disabled={busy !== null || api === null}
      onclick={() =>
        run('Opened print view', () =>
          api!.print({
            title: 'Orders',
            columns: exportColumns,
            orientation: 'landscape',
          }),
        )}
    >
      Print…
    </button>
    {#if busy}
      <span class="text-xs xp-note">{busy}…</span>
    {:else if lastAction}
      <span class="text-xs text-green-600 dark:text-green-400">{lastAction}</span>
    {/if}
    {#if errorMsg}
      <span class="text-xs text-red-600 dark:text-red-400">{errorMsg}</span>
    {/if}

    <label class="ml-auto flex items-center gap-2 text-xs xp-note">
      <input type="checkbox" bind:checked={licensed} disabled={!appKey} class="h-4 w-4" />
      Licensed (uncheck to see the unlicensed watermark + console nudge)
    </label>
  </div>

  <div class="flex-1 min-h-0">
    <SvGrid responsive={true}
      columnResize
      data={rows}
      columns={columns}
      features={features}
      filterMode="menu"
      selectionMode="both"
      showRowSelection={true}
      showRowNumbers={true}
      showGroupingControls={false}
      enableCellSelection={true}
      enableInlineEditing={false}
      rowHeight={36}
      containerHeight="100%"
      fitColumns={true}
      onApiReady={onReady}
    />
  </div>

  <footer class="text-xs shrink-0 xp-note">
    Pro feature - gated by <code>setLicenseKey()</code>. Without your license key
    the feature still runs, but the grid shows a watermark linking to
    svgrid.com/pricing. A wrong key never throws; it behaves like no key.
  </footer>
</section>

<style>
  .xp-note { color: var(--sg-muted, #475569); }
  .xp-btn {
    border-color: var(--sg-border, #cbd5e1);
    background: var(--sg-bg, #ffffff);
    color: var(--sg-fg, #0f172a);
  }
  .xp-btn:hover:not(:disabled) { background: var(--sg-row-hover-bg, #f8fafc); }
  .xp-btn-primary {
    border-color: var(--sg-accent, #4f46e5);
    background: var(--sg-accent, #4f46e5);
    color: var(--sg-on-accent, #fff);
  }
  .xp-btn-primary:hover:not(:disabled) {
    background: var(--sg-accent, #4f46e5);
    filter: brightness(1.08);
  }
</style>
```

[View this example on GitHub](https://github.com/sv-grid/sv-grid/blob/main/examples/src/demos/21-export-and-print.svelte)
