Skip to content

useExport ​

Downloads rows as CSV, Excel or PDF. Neither heavy library reaches your bundle until someone actually exports.

Open in demo

Import ​

ts
import { useExport } from '@shared/composables/useExport'

Signature ​

ts
function useExport<T extends Record<string, unknown>>(): {
  exportCSV: (data: T[], columns: ColumnDef<T>[], filename: string) => void
  exportExcel: (data: T[], columns: ColumnDef<T>[], filename: string) => Promise<void>
  exportPDF: (data: T[], columns: ColumnDef<T>[], filename: string) => Promise<void>
}

Returns ​

MemberTypeProducesLibrary
exportCSVsynchronous{filename}.csvNone. Written by hand.
exportExcelPromise<void>{filename}.xlsxxlsx, lazy-loaded.
exportPDFPromise<void>{filename}.pdfjspdf and jspdf-autotable, lazy-loaded.

filename carries no extension — each function adds its own.

Each takes the columns you want in the file, so a reader can export a subset. ColumnDef supplies key for the value, label for the header and, when it has one, render for what the cell shows.

What lands in the file ​

CSVExcelPDF
HeadersThe label in the active languageThe label in the active languageThe label in English
CellsWhat the table showsWhat the table showsWhat the table shows, in English
NumbersPlain numbersNumber cells a spreadsheet can sumText

A label can be an i18n key or plain text. A key is resolved, so the header reads Name rather than users.fields.name.

A cell is exported as its column renders it. When the column has a render, the file gets that text: an option label in the active language instead of active, and the local date the table shows instead of an ISO timestamp. A column with no render exports its value as it is. A number value stays a number in CSV and Excel, so a total can be summed.

The PDF prints headers and cells in the base language, English. jsPDF's built-in font has no Arabic glyphs, so a translated PDF would print broken characters. Embed a Unicode font with doc.addFont if you need every language in the PDF.

Example ​

vue
<script setup lang="ts">
import type { ColumnDef } from '@shared/components/common/DataTable/types'
import { useExport } from '@shared/composables/useExport'
import { formatCurrency } from '@shared/utils/format'

interface Invoice extends Record<string, unknown> {
  reference: string
  customer: string
  total: number
}

const rows: Invoice[] = [
  { reference: 'INV-2026-0418', customer: 'Northwind', total: 4820 },
  { reference: 'INV-2026-0419', customer: 'Contoso', total: 1140.5 },
]

const columns: ColumnDef<Invoice>[] = [
  { key: 'reference', label: 'Reference' },
  { key: 'customer', label: 'Customer' },
  { key: 'total', label: 'Total', render: value => formatCurrency(Number(value), 'USD') },
]

const { exportCSV, exportExcel, exportPDF } = useExport<Invoice>()
</script>

<template>
  <div class="flex flex-wrap gap-2">
    <button type="button" @click="exportCSV(rows, columns, 'invoices')">
      CSV
    </button>
    <button type="button" @click="exportExcel(rows, columns, 'invoices')">
      Excel
    </button>
    <button type="button" @click="exportPDF(rows, columns, 'invoices')">
      PDF
    </button>
  </div>
</template>

Notes ​

  • The CSV is written by hand, and every cell goes through JSON.stringify. That quotes and escapes it, so a comma, a quote or a newline inside a value cannot break the file.
  • It starts with a UTF-8 byte-order mark, because without one Excel opens a UTF-8 CSV as the system code page and mangles every accented name.
  • xlsx and jspdf are imported inside their exporters, so a project that never exports never downloads them.
  • A missing value is written as an empty string rather than undefined.
  • xlsx installs from SheetJS's own CDN, https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz, not from the npm registry, whose copy stopped at 0.18.5. 0.20.3 carries the fixes for both published xlsx security reports. npm audit does not check a dependency installed from a URL, so watch cdn.sheetjs.com for new versions yourself.
  • The DataTable's export menu and the export button appear only while config.features.exportSystem is on. The composable itself does not read the flag.
  • The download is triggered by a temporary anchor, and the object URL is revoked straight after, so nothing is left holding the blob.