PDF Export
TableCrafter turns the current table view into a genuine, server-generated PDF 1.4 document — a real %PDF- file with a valid object structure and cross-reference table, not an HTML page renamed to .pdf. This page explains how to enable the export, how the PDF is produced, and the layout limits you should plan around.
Overview
PDF is one of three formats produced by the canonical export handler in includes/class-tc-export-handler.php (alongside CSV and a real OOXML XLSX workbook). When a visitor picks PDF from the export menu, the already-rendered rows and column configuration are sent to the server, written into a structurally valid PDF, stored as a short-lived temporary file, and streamed back as a download.
The PDF is rendered as monospaced-style text lines using the standard Helvetica Type1 font on a single US Letter page. It is intentionally a lightweight, dependency-free generator: no TCPDF, FPDF, or headless-browser step is involved, so it works on any WordPress host without extra extensions.
The file is a real, openable PDF. It begins with %PDF-1.4 and ends with %%EOF, and the xref offsets are computed from the actual byte positions of each object — so it opens in any compliant PDF viewer.
Enabling export on a table
PDF export is part of the table-level export tools, which are off by default. Switch them on with the export attribute of the [tablecrafter] shortcode.
// Enable the export tools (CSV / Excel / PDF + Copy to Clipboard)
[tablecrafter source="https://example.com/data.json" export="true"]
With export="true", the rendered container carries data-export="true", and the frontend script attaches an Export Data ▾ dropdown plus a Copy to Clipboard button above the table. Because the default export configuration lists more than one format (['csv', 'excel', 'pdf']), the dropdown is shown and PDF appears as the 📑 PDF option.
Shortcode attributes relevant to export
| Attribute | Default | Required | Description |
|---|---|---|---|
| export | false | Optional | Master switch for the export tools. Accepts true, 1, or yes. Must be on for PDF export to appear. |
| source | "" | Required | Data source URL. Without it the table renders an empty-source placeholder and there is nothing to export. |
| include | "" | Optional | Comma-separated columns to keep. Narrows what ends up in the PDF. |
| exclude | "" | Optional | Comma-separated columns to drop from the table and the export. |
| filters | true | Optional | When filters are active, the export reflects the current filtered view (see "Filtered exports" below). |
There is no separate format="pdf" shortcode attribute. The format is chosen by the visitor at click time from the export dropdown; the shortcode only decides whether the tools are available.
How a PDF export flows end to end
- The visitor opens the Export Data ▾ menu and clicks 📑 PDF. This calls
handleExportFormat('pdf'), which routes todownloadEnhanced('pdf'). - The script collects the exportable rows and columns, shows a "Generating PDF export..." overlay, and POSTs a
FormDatarequest toadmin-ajax.phpwithaction=tc_export_data,format=pdf, the JSONdataandcolumns, afilename, anoptionsblob, and anonce. - The server verifies the
tc_export_nonce, checks thereadcapability, then hands the payload toTC_Export_Handler::export_client_data()→export_data()→export_pdf(). create_basic_pdf()builds the text lines andrender_pdf_document()assembles the PDF bytes. The file is saved to a protected temp directory and cached in a transient for 5 minutes.- The response returns a
download_url(actiontc_download_export, guarded by atc_download_nonce). The browser follows it; the server streams the file withContent-Type: application/pdf, then deletes both the temp file and the transient.
// Simplified client request the PDF option fires
formData.append('action', 'tc_export_data');
formData.append('format', 'pdf');
formData.append('data', JSON.stringify(exportableData));
formData.append('columns', JSON.stringify(exportableColumns));
formData.append('filename', 'table-export');
formData.append('nonce', this.getExportNonce());
What the PDF contains
The text lines painted into the content stream are built by build_pdf_text_lines() in this order:
- Title line — the sanitized
filename(the export base name). - Header row — the column display labels joined with
|, followed by a dashed separator, when headers are included. - Data rows — each row's cells joined with the same pipe separator.
- Metadata footer (optional) — an "Export Information" block with the generation timestamp, total record count, and any applied filters/sort, when metadata is enabled.
Each cell passes through format_cell_value(), so values are cleaned and normalized before they reach the page:
| Value type | Treatment in the PDF |
|---|---|
| HTML markup | Tags stripped via wp_strip_all_tags() — only plain text is drawn. |
Dates (YYYY-MM-DD…) | Reformatted using the active date_format (default Y-m-d). |
| Numbers | Run through number_format() to two decimals. |
| Arrays | Serialized to a JSON string. |
Page layout and limits
The renderer (render_pdf_document()) is deliberately constrained to guarantee a well-formed, single-page file. Understanding these fixed values helps you size your data sensibly.
| Layout property | Value |
|---|---|
| PDF version | 1.4 |
| Page size | US Letter — MediaBox [0 0 612 792] points (portrait) |
| Font | Helvetica (standard Type1), size 9 pt |
| Line height (leading) | 12 pt |
| Margins | Left 40 pt; first baseline at 760 pt; bottom 40 pt |
| Max lines per page | ~60 (computed from (760 − 40) / 12) |
| Max characters per line | 110 — longer lines are truncated with an ellipsis (…) |
Single-page, text-only output. The PDF is always one US Letter page. If your data plus title and headers exceed the line budget, the remaining rows are dropped and a final line reads "... output truncated to fit one page". Wide rows are clipped to 110 characters. For large or wide datasets, prefer CSV or XLSX, or narrow the table with include/exclude and your active filters before exporting.
The pdf options blob (advisory only)
The frontend sends a small PDF options object — built from config.advancedExport.pdf — containing title, subtitle, orientation, and footer:
// Default advancedExport.pdf config sent with the request
pdf: {
orientation: 'landscape',
title: 'Data Export Report',
subtitle: '',
footer: 'Generated by TableCrafter'
}
In TableCrafter 3.5.6 the server PDF renderer paints the table text only and does not apply these title/subtitle/orientation/footer values — the page is always portrait Letter and the title shown is the export filename. Treat this options blob as forward-looking metadata, not as layout controls. To change the visible title, set the export filename instead.
Filtered exports and column control
By default the export respects what the visitor is currently looking at. The frontend's exportFiltered setting is true, so getExportableData() returns the filtered/searched view rather than the full dataset. Columns marked non-exportable (column.exportable === false) are excluded by getExportableColumns(). Combine this with the shortcode's include/exclude attributes to keep PDFs focused and within the one-page budget.
Metadata and export templates
When metadata is enabled, the PDF appends an "Export Information" block (generation time, total records, applied filters, applied sort). Metadata is governed by the export template. Templates are defined in get_export_templates() and can be customized via the tc_export_templates filter:
| Template | include_metadata | date_format |
|---|---|---|
| default | false | Y-m-d |
| business | true | M j, Y |
| data_analysis | true | c (ISO 8601) |
// Register a custom export template (PHP, e.g. in your theme functions.php)
add_filter('tc_export_templates', function ($templates) {
$templates['audit'] = [
'name' => 'Audit Report',
'include_metadata' => true,
'date_format' => 'M j, Y',
];
return $templates;
});
Listening for export events
When a PDF download succeeds, TableCrafter invokes the optional onExport callback with the format, the exported data and columns, the final filename, and the file size — useful for analytics or audit logging.
new TableCrafter(el, {
exportable: true,
exportFilename: 'quarterly-report',
onExport(payload) {
if (payload.format === 'pdf') {
console.log('PDF exported:', payload.filename, payload.size);
}
}
});
Security and cleanup
- Nonces. The export request requires a valid
tc_export_nonce; the download link requires atc_download_nonce. Both are issued server-side. - Capability. The export AJAX handler requires the
readcapability, so logged-out access depends on your site's policy. - Protected temp files. Generated PDFs are written to
wp-content/uploads/tablecrafter-exports/with an.htaccess(deny-all) and anindex.phpguard. Filenames use a random UUID with atc_export_prefix. - Self-cleaning. The temp file and its transient are deleted immediately after the download streams. The transient also expires after 5 minutes, and
cleanup_old_exports()removes stragglers older than the max age (default 3600 seconds).
Sanitization runs on the base filename via sanitize_file_name(), and the .pdf extension is appended server-side. Set exportFilename (or the AJAX filename) to a clean, human-readable name — it doubles as the document's title line.
Quick reference
| Item | Value |
|---|---|
| Export AJAX action | tc_export_data (nonce tc_export_nonce) |
| Download AJAX action | tc_download_export (nonce tc_download_nonce) |
| MIME type | application/pdf |
| Dropdown classes | .tc-export-dropdown-wrapper, .tc-export-main-btn, .tc-export-option.tc-export-pdf |
| Handler | TC_Export_Handler::export_pdf() → create_basic_pdf() |
| Plugin version | 3.5.6 |
Next steps: For tabular data that exceeds one page, use the genuine spreadsheet output documented in export-excel.html, or the lightweight, fully-faithful option in export-csv.html.