Excel (XLSX) Export

TableCrafter exports your table to a genuine OOXML .xlsx workbook assembled server-side with PHP's ZipArchive — a real spreadsheet that opens natively in Excel, Google Sheets, and LibreOffice, never a CSV or HTML file renamed to .xlsx.

OOXML ZipArchive Server-side Excel & Sheets Filter-aware

Overview

When a visitor picks Excel from a table's export menu, the browser sends the currently displayed rows and column configuration to WordPress over AJAX. The plugin's export handler (includes/class-tc-export-handler.php) builds a valid OOXML package on disk, stores it in a protected uploads directory, and returns a one-time download link. The result is a true .xlsx workbook with the correct MIME type application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.

Two key implementation details make this honest:

Enabling export on a table

Excel export rides on the same export switch as CSV and PDF. Add the export attribute to the [tablecrafter] shortcode:

<!-- Minimal: enable the export menu (CSV / Excel / PDF) -->
[tablecrafter source="https://example.com/sales.csv" export="true"]

A richer table that searches, paginates, and exports the filtered view:

[tablecrafter
  source="https://example.com/q3-orders.csv"
  search="true"
  filters="true"
  export="true"
  per_page="25"
  sort="total:desc"]

In the block editor, the same control is exposed as the Enable Export Tools toggle on the TableCrafter (Data Table) block, which sets the data-export attribute the frontend reads.

Shortcode attributes relevant to export

AttributeRequiredDescription
sourceRequiredURL of the data source (CSV, Google Sheet, Airtable, etc.). Without it the table cannot render or export.
exportOptionalSet to true (also accepts 1 / yes) to render the export controls. Defaults to false.
filtersOptionalEnables column filters. The Excel export reflects the filtered view (see below). Defaults to true.
searchOptionalEnables global search; matching rows are what gets exported.
include / excludeOptionalComma-separated column lists that shape which columns appear in the table, and therefore in the workbook.

Using the export menu

With export="true", TableCrafter renders an export control group (.tc-export-controls) above the table. When more than one format is available, the controls render as a dropdown:

Choosing Excel triggers a loading overlay (.tc-export-loading, "Generating EXCEL export..."), then the file downloads automatically and a success notification reports the file size.

💡

A "Copy to Clipboard" button (.tc-copy-clipboard) sits beside the export menu. It copies tab-separated values you can paste straight into a spreadsheet cell — handy for quick, in-place transfers without generating a file.

What's inside the .xlsx file

The handler builds a minimal-but-valid OOXML package. Each part below is added to the ZIP with ZipArchive::addFromString():

Package partPurpose
[Content_Types].xmlDeclares the content types for the workbook and worksheet parts so Excel knows how to read them.
_rels/.relsPackage-level relationship pointing at the workbook as the office document.
xl/workbook.xmlThe workbook definition. Holds one sheet named TableCrafter Export.
xl/_rels/workbook.xml.relsRelationship linking the workbook to its worksheet.
xl/worksheets/sheet1.xmlThe actual data: one <row> per record with addressed <c> cells.

Cells are written as inline strings (t="inlineStr" with <is><t>), and every value is HTML-escaped, so commas, quotes, and Unicode survive intact without the encoding surprises that plague CSV. Cell references use proper spreadsheet addressing (A1, B1, ... AA1) generated from the column index.

<!-- Shape of xl/worksheets/sheet1.xml (abbreviated) -->
<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
  <sheetData>
    <row r="1">
      <c r="A1" t="inlineStr"><is><t>Product</t></is></c>
      <c r="B1" t="inlineStr"><is><t>Total</t></is></c>
    </row>
    <row r="2">
      <c r="A2" t="inlineStr"><is><t>Widget</t></is></c>
      <c r="B2" t="inlineStr"><is><t>42.00</t></is></c>
    </row>
  </sheetData>
</worksheet>
ℹ️

The first row is a header row built from your column labels (the human-friendly label for each column, falling back to the raw field key). Header output can be suppressed by callers that set include_headers to false; the frontend export includes them by default.

What gets exported: rows, columns, and formatting

The Excel export honors the table's live state rather than re-fetching the raw source:

Export templates control the formatting and optional metadata. They are filterable, so you can register your own:

// Add a custom export template (e.g. in your theme functions.php)
add_filter('tc_export_templates', function ($templates) {
    $templates['finance'] = [
        'name'             => 'Finance Report',
        'include_metadata' => true,
        'date_format'      => 'M j, Y',
        'number_format'    => '$0.00',
    ];
    return $templates;
});

Built-in templates are default, business (adds a metadata footer with generation time and record count), and data_analysis (ISO-8601 dates, high-precision numbers).

Opening the file in Excel and Sheets

ℹ️

All cells are written as text (inline strings). This guarantees lossless values — leading zeros, long IDs, and codes are preserved exactly. If you need a column to behave as a number or date inside the workbook, apply Excel/Sheets cell formatting after opening, or set up your source so the value is unambiguous.

How the download is delivered and secured

Excel export is a two-step AJAX round trip, both endpoints nonce-protected:

  1. The browser POSTs the rows, columns, filename, and options to admin-ajax.php with action=tc_export_data, guarded by the tc_export_nonce and a read capability check. The handler builds the .xlsx, stores the result in a transient (tc_export_*) for five minutes, and returns a one-time download_url.
  2. Clicking that URL hits action=tc_download_export (guarded by tc_download_nonce), which streams the file with the correct Content-Type and Content-Disposition: attachment headers, then deletes both the temp file and the transient.

Generated files live in a protected directory, wp-content/uploads/tablecrafter-exports/, created on demand with an .htaccess "deny all" rule and a silent index.php so they can't be browsed directly. Filenames are prefixed tc_export_ with a UUID. Stale files are purged after download and, as a backstop, a cleanup routine removes any export older than an hour.

DetailValue
AJAX action (generate)tc_export_data
AJAX action (download)tc_download_export
Noncestc_export_nonce, tc_download_nonce
MIME typeapplication/vnd.openxmlformats-officedocument.spreadsheetml.sheet
Temp directoryuploads/tablecrafter-exports/ (deny-all)
Link lifetime5 minutes (transient)

Hooking into the export

On the JavaScript side, the onExport callback fires after a successful Excel download, receiving the format, the exported data and columns, the resolved filename, and the byte size — useful for analytics or post-export UI:

new TableCrafter('#my-table', {
  data: 'https://example.com/orders.csv',
  exportable: true,
  onExport: (e) => {
    // e.format === 'excel', plus e.filename, e.size, e.data, e.columns
    console.log('Exported', e.filename, e.size);
  }
});
⚠️

XLSX generation requires PHP's ZipArchive extension. If it's unavailable, the handler throws and the export returns an error instead of producing a corrupt file. Confirm ext-zip is enabled on your host before relying on Excel export.

Next steps

For the lighter, header-row-and-rows format see export-csv.html, and for a paginated print-ready document see export-pdf.html.