Export - Formatting, scope + XLS

Faithful export: currency / date / percent render as shown (toggle raw values), pick a row scope (view / selected / all), legacy .xls (Excel 2003 XML, no jszip), an exportValue hook, and the drop-in SvExportMenu. (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.

About this example

Faithful export from the Svelte 5 data grid. Currency, date and percent values export exactly as shown on screen, with a rawValues toggle for the underlying numbers; a row scope picks the current view, only selected rows or all rows; legacy .xls in Excel 2003 XML format needs no zip library and keeps numbers numeric; an exportValue hook handles a custom-rendered column; and the drop-in SvExportMenu component supplies the format picker, scope and progress.

Showcases the export improvements:

  • Faithful values: currency / date / percent export exactly as shown on screen (toggle rawValues to see the underlying raw values).
  • Row scope: export the current view, only selected rows, or all rows.
  • Legacy .xls (Excel 2003 XML) for old-Excel compatibility - no jszip needed, and numbers stay numeric.
  • exportValue hook for a custom-rendered column.
  • The drop-in <SvExportMenu> component (format picker + scope + progress).

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), price (Price), discount (Discount), country (Country), company (Company), product (Product), sellDate (Sell date), price (Price), discount (Discount), orderId (Order)

SvGridApi methods called: api.exportData()

Frequently asked questions

How do I export only the selected rows?

Pass rows: 'selected' to exportData, or pick it in SvExportMenu's scope control. rows: 'all' ignores the current filter, the default 'displayed' exports the view, and an explicit array overrides all three.

When would I use .xls instead of .xlsx?

For consumers on old Excel versions or tools that only read the 2003 XML format. It is written as plain XML, so it needs no zip library, and numeric cells stay numeric.

How do I control a custom column's exported value?

Set exportValue on the column definition. It receives the row and returns the text or number to write, which is how a snippet-rendered column exports something meaningful.

Related documentation

Related articles

Source code (201-export-formatting-and-xls.svelte)

<script lang="ts">
  /**
   * 201. Export: faithful formatting, scope, and legacy XLS (Enterprise)
   * -------------------------------------------------------------------
   * Showcases the export improvements:
   *   - Faithful values: currency / date / percent export exactly as shown
   *     on screen (toggle `rawValues` to see the underlying raw values).
   *   - Row scope: export the current view, only selected rows, or all rows.
   *   - Legacy .xls (Excel 2003 XML) for old-Excel compatibility - no jszip
   *     needed, and numbers stay numeric.
   *   - `exportValue` hook for a custom-rendered column.
   *   - The drop-in <SvExportMenu> component (format picker + scope + progress).
   */
  import {
    SvGrid,
    tableFeatures,
    rowSortingFeature,
    columnFilteringFeature,
    rowSelectionFeature,
    type GridColumns,
    type ConditionalFormat,
    type SvGridApi,
  } from '@svgrid/grid'
  import {
    installEnterprise,
    SvExportMenu,
    type EnterpriseGridApi,
    type ExportColumn,
  } from '@svgrid/enterprise'
  import { makeOrders, type Order } from '../shared/seed'

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

  // Add a synthetic `discount` (0..40 points) so we have a percent column.
  type Row = Order & { discount: number }
  let rows = $state<Row[]>(makeOrders(200).map((o, i) => ({ ...o, discount: (i * 7) % 40 })))
  let api = $state<EnterpriseGridApi<typeof features, Row> | null>(null)
  let rawValues = $state(false)
  let scope = $state<'displayed' | 'selected' | 'all'>('displayed')
  let lastAction = $state('')
  let errorMsg = $state<string | null>(null)

  // Priced with a real number `format` so the export renders "$1,234.00",
  // a percent column stored as 0..100 points, and a formatted date.
  const columns: GridColumns<Row> = [
    { field: 'company', header: 'Company', width: 150 },
    { field: 'product', header: 'Product', width: 160 },
    {
      field: 'sellDate',
      header: 'Sell date',
      width: 120,
      format: { type: 'date', pattern: 'y-m-d' },
    },
    {
      field: 'price',
      header: 'Price',
      width: 120,
      format: { type: 'currency', currency: 'USD' },
    },
    {
      field: 'discount',
      header: 'Discount',
      width: 110,
      format: { type: 'percent', valueIsPercentPoints: true },
    },
    { field: 'country', header: 'Country', width: 100 },
  ]

  // Conditional formatting: a color scale on discount + a data bar on price.
  // These render on screen AND carry into the pdf / xlsx / html exports.
  const conditionalFormats: ConditionalFormat<Row>[] = [
    { type: 'colorScale', columns: ['discount'], min: '#dcfce7', mid: '#fef9c3', max: '#fee2e2' },
    { type: 'dataBar', columns: ['price'], color: '#3b82f6' },
  ]

  // A custom "Order" column whose export value comes from an exportValue hook
  // (as if the on-screen cell were a custom snippet the file can't serialize).
  const exportColumns: ExportColumn<Row>[] = [
    // `link` turns Company into a clickable hyperlink in xls / html / pdf exports.
    { field: 'company', header: 'Company', link: (r) => `https://www.google.com/search?q=${encodeURIComponent(r.company)}` },
    { field: 'product', header: 'Product' },
    { field: 'sellDate', header: 'Sell date', format: { type: 'date', pattern: 'y-m-d' } },
    { field: 'price', header: 'Price', format: { type: 'currency', currency: 'USD' } },
    { field: 'discount', header: 'Discount', format: { type: 'percent', valueIsPercentPoints: true } },
    { field: 'orderId', header: 'Order', exportValue: (r) => `#${r.orderId}` },
  ]

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

  async function run(label: string, format: 'xlsx' | 'xls' | 'csv' | 'pdf') {
    if (!api) return
    errorMsg = null
    try {
      await api.exportData({
        format,
        filename: 'orders',
        columns: exportColumns,
        rows: scope,
        rawValues,
        conditionalFormats,
        pageOrientation: 'landscape',
      })
      lastAction = label
    } catch (err) {
      errorMsg = err instanceof Error ? err.message : String(err)
    }
  }
</script>

<section class="flex flex-col flex-1 min-h-0 gap-3">
  <div class="ex-lead text-sm shrink-0">
    Exports reflect what you see: currency, dates, and percents are formatted.
    Toggle <strong>raw values</strong> to export underlying numbers, pick a
    <strong>scope</strong>, and try <strong>.xls</strong> for legacy Excel.
  </div>

  <div class="flex flex-wrap items-center gap-2 shrink-0">
    <!-- Drop-in menu: format picker + scope + column picker + copy + progress -->
    <SvExportMenu
      {api}
      filename="orders"
      label="Export menu"
      formats={['xlsx', 'xls', 'pdf', 'csv', 'json', 'md']}
      allowPrint
      {conditionalFormats}
    />

    <span class="ex-divider mx-1 h-5 w-px"></span>

    <!-- Manual buttons wired to the scope + rawValues toggles below -->
    <button type="button" class="btn" disabled={!api} onclick={() => run('Excel (.xlsx)', 'xlsx')}>xlsx</button>
    <button type="button" class="btn" disabled={!api} onclick={() => run('Excel 97-2003 (.xls)', 'xls')}>xls</button>
    <button type="button" class="btn" disabled={!api} onclick={() => run('CSV', 'csv')}>csv</button>
    <button type="button" class="btn" disabled={!api} onclick={() => run('PDF', 'pdf')}>pdf</button>

    <label class="ex-label flex items-center gap-1.5 text-xs">
      Scope
      <select bind:value={scope} class="ex-select rounded px-2 py-1 text-xs">
        <option value="displayed">Current view</option>
        <option value="selected">Selected</option>
        <option value="all">All rows</option>
      </select>
    </label>
    <label class="ex-label flex items-center gap-1.5 text-xs">
      <input type="checkbox" bind:checked={rawValues} class="h-4 w-4" />
      Raw values
    </label>

    {#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}
      columnResize
      data={rows}
      columns={columns}
      features={features}
      conditionalFormats={conditionalFormats}
      filterMode="menu"
      selectionMode="both"
      showRowSelection={true}
      showRowNumbers={true}
      enableCellSelection={true}
      rowHeight={36}
      containerHeight="100%"
      fitColumns={true}
      onApiReady={onReady}
    />
  </div>

  <footer class="ex-foot text-xs shrink-0">
    Enterprise feature. <code>.xls</code> is the Excel 2003 XML Spreadsheet
    format - opens in Excel 97-2003+ with no <code>jszip</code> dependency and
    keeps numbers numeric. The <code>#Order</code> column uses an
    <code>exportValue</code> hook.
  </footer>
</section>

<style>
  .btn {
    border-radius: 0.375rem;
    border: 1px solid var(--sg-border, rgb(203 213 225));
    padding: 0.375rem 0.75rem;
    font-size: 0.875rem;
    font-weight: 500;
    background: var(--sg-bg, white);
    color: var(--sg-fg, rgb(15 23 42));
  }
  .btn:disabled { opacity: 0.5; }
  .ex-lead { color: var(--sg-fg, #475569); }
  .ex-foot { color: var(--sg-muted, #64748b); }
  .ex-label { color: var(--sg-muted, #475569); }
  .ex-divider { background: var(--sg-border, #cbd5e1); }
  .ex-select {
    border: 1px solid var(--sg-input-border, #cbd5e1);
    background: var(--sg-input-bg, #ffffff);
    color: var(--sg-fg, #0f172a);
  }
</style>

View this example on GitHub

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.