Lattice Grid Buy a licence

demo D118

CSV export in htmx

Delimiters, quoting, which rows, and per-cell processing

export.csv()

This grid writes its current data out as CSV, with control over delimiters, quoting, which rows are included and per-cell processing. It gets a report into any spreadsheet or downstream system in a format everything reads, straight from the browser.

Building…
Loading a live grid…

This is the htmx version. A server-rendered table is upgraded in place, so the page is readable before any script runs and gains the full grid when the module loads, which suits a progressively enhanced app.

The configuration

<link rel="stylesheet" href="https://cdn.latticegrid.dev/lattice-grid@1.107.0/lattice-grid.min.css">

<!-- A real, server-rendered table: readable with JavaScript off. data-field on
     each header names the column that cell becomes, and data-id on each row is
     the key the grid carries it under. -->
<table id="csv-export-table" data-lattice-grid>
  <thead>
    <tr>
      <th data-field="ref">Invoice</th>
      <th data-field="supplier">Supplier</th>
      <th data-field="region">Region</th>
      <th data-field="category">Category</th>
      <th data-field="quantity">Qty</th>
      <th data-field="amount">Amount</th>
      <th data-field="note">Note</th>
      <th data-field="internal">Internal</th>
    </tr>
  </thead>
  <tbody>
    <tr data-id="L000001"><td>INV-2026-1000</td><td>Northgate Fibre, Ltd</td><td>Cardiff</td><td>Circuit rental</td><td>24</td><td>5630.64</td><td></td><td>j.okafor · margin 29.3%</td></tr>
    <tr data-id="L000002"><td>INV-2026-1000</td><td>Selby Ducting</td><td>Cardiff</td><td>Install</td><td>23</td><td>4525.69</td><td>Marked "do not renew" by the account team</td><td>r.dubois · margin 8.3%</td></tr>
    <tr data-id="L000003"><td>INV-2026-1000</td><td>Selby Ducting</td><td>Manchester</td><td>Install</td><td>4</td><td>2025.95</td><td>Marked "do not renew" by the account team</td><td>j.okafor · margin 26.5%</td></tr>
    <!-- the rest of the rows, rendered by your server -->
  </tbody>
</table>

<script type="module">
  import { hydrateTable } from 'https://cdn.latticegrid.dev/lattice-grid@1.107.0/modules/htmx.esm.min.js';

  // The markup carries the rows; a server-rendered table's column types are
  // declared in the config hydrateTable takes. The grid replaces the table
  // where it stood.
  const grid = hydrateTable(document.getElementById('csv-export-table'), {
    selection: 'multiple',
    // Every export here is a click: the grid writes the file the moment it is
    // asked, so the request belongs on a button rather than on page load.
    toolPanel: {
      side: 'left',
      panels: ['columns', 'filters', 'quick'],
      exportName: 'supplier-charges',
      actions: [
        'undo', 'redo', 'restore', 'maximise', '-',
        'export',
        {
          name: 'csv-semicolon',
          title: 'Download CSV, semicolon-delimited',
          icon: 'download',
          run: ({ grid }) => grid.export.csv({
            download: true,
            fileName: 'supplier-charges-semicolon',
            delimiter: ';',
          }),
        },
        {
          // The same call with rows: 'selected'. With nothing selected the guard
          // here skips the write rather than saving a file of headers alone.
          name: 'csv-selected',
          title: 'Download CSV of the selected rows only',
          icon: 'check',
          run: ({ grid, keys }) => {
            if (!keys.length) return;
            grid.export.csv({ download: true, fileName: 'supplier-charges-selected', rows: 'selected' });
          },
        },
      ],
    },
    columns: [
      { field: 'ref', title: 'Invoice', layout: { pin: 'start', width: 140 } },
      { field: 'supplier', title: 'Supplier', layout: { flex: 1, min: 200 }, filter: { type: 'set' } },
      { field: 'region', title: 'Region', filter: { type: 'set' } },
      { field: 'category', title: 'Category', filter: { type: 'set' } },
      { field: 'quantity', title: 'Qty', type: 'number', layout: { width: 110 } },
      { field: 'amount', title: 'Amount', type: 'number', layout: { width: 150 },
        format: { style: 'currency', currency: 'GBP', decimals: 2 }, total: 'sum',
        filter: { type: 'number' } },
      { field: 'note', title: 'Note', layout: { width: 280 } },
      // On screen and out of the file. What a grid shows and what it exports are
      // separate decisions, and this is the column that proves it.
      {
        field: 'internal', title: 'Internal', layout: { width: 200 },
        export: { csv: false },
      },
    ],
  });

  // The same writer, called from your own control.
  grid.export.csv({ download: true, fileName: 'supplier-charges', delimiter: ',' });
</script>

Exporting grid data to CSV with control over delimiters, quoting and rows

CSV export writes the grid’s current data out as plain text, one row per line, in a format any spreadsheet, script or downstream system can read without a library of its own. A developer reaches for it when a report needs to leave the browser as a file: a filtered result set for a colleague, a scheduled download, or an input to a tool that only accepts flat text. Lattice Grid exposes this through export.csv(), which takes the delimiter, the quoting rule and the row selection as options, so a comma-separated file for one audience and a tab-separated file for another come from the same call with different arguments. Row selection chooses between the full dataset, the current filtered view, or a specific selection, which matters because a filtered grid and its unfiltered source are not the same export unless asked to be. A per-cell processing hook runs before each value is written, letting an application reformat a date or apply the same redaction rule it uses on screen so an export never leaks a value the grid itself would mask. Because the write walks the underlying row store rather than the rendered, virtualised viewport, exporting a million rows costs the same per row as exporting ten, without first requiring every row to have been scrolled into view.

How do I export only the filtered or selected rows from a JavaScript data grid to CSV?

Pass the row selection option to export.csv(): it accepts the full dataset, the rows currently matching the grid’s filters, or an explicit selection. Combined with the per-cell processing hook, this produces a CSV that reflects exactly what a user sees on screen, including any redaction applied to individual cells, rather than the grid’s raw underlying data.