Extending: Custom Data Sources
TableCrafter resolves every data source through a single URL-driven dispatcher and exposes a small, well-defined set of WordPress filters, a cron action, browser events, and CSS variables. This page documents the real extension points in v3.5.6 and shows exactly where custom behavior can and cannot be wired in.
How source resolution actually works
All data flows through one method: TC_Data_Fetcher::fetch() in includes/class-tc-data-fetcher.php. The shortcode handler passes the source attribute to this dispatcher, which inspects the URL and routes it to the correct handler. Resolution happens in a fixed order:
- Local files — if the URL begins with your site URL, home URL, or the plugin URL, the file is read from disk (whitelisted to
ABSPATH,WP_CONTENT_DIR, and the plugin directory)..jsonand.csvextensions are parsed. - Airtable — URLs starting with the
airtable://protocol are delegated toTC_Airtable_Source. - CSV / Google Sheets — a Google Sheets
docs.google.com/spreadsheets/d/…URL or any URL ending in.csvis delegated toTC_CSV_Source. - Remote JSON — anything else is fetched as a JSON API endpoint.
There is no public filter or action to register a brand-new source handler in v3.5.6. The four branches above are hardcoded if/elseif dispatch in TC_Data_Fetcher::fetch(). The plugin ships only two filters site-wide: tablecrafter_trusted_ip_headers and tc_export_templates. Any guide that tells you to call a register_source() filter is describing a capability this version does not have. The sections below cover what genuinely is extensible.
The source-class pattern (code-level extension)
The built-in handlers follow a consistent convention you can mirror if you maintain a fork or companion plugin. Each source is a small class under includes/sources/ with static methods that return either a normalized array of associative rows or a WP_Error:
// includes/sources/class-tc-csv-source.php
class TC_CSV_Source {
public static function fetch(string $url) { /* … */ }
public static function parse(string $csv_string): array { /* … */ }
}
// includes/sources/class-tc-airtable-source.php — uses the airtable:// protocol
class TC_Airtable_Source {
public static function parse_url(string $url) { /* airtable://baseId/tableName?view=… */ }
public static function fetch(string $base_id, string $table_name, string $token, array $params = []) { /* … */ }
}
The contract every handler honors:
| Requirement | Detail |
|---|---|
| Return shape | A flat array of rows, each row an associative array keyed by column name. Header keys come from the first object/CSV row. |
| Error shape | Return a WP_Error on failure; TC_Data_Fetcher surfaces an admin error helper for users with manage_options, and a graceful message otherwise. |
| Protocol | Pick a recognizable URL pattern (a custom scheme like airtable://, or a file extension) so the dispatcher branch can identify it. |
Because dispatch is hardcoded, a new protocol requires editing the if/elseif chain in TC_Data_Fetcher::fetch() to add your branch. This is a source-modification (fork) approach, not a drop-in hook. Honor the WP_Error contract and the row-array return shape so the rest of the pipeline — caching, root extraction, column processing, sorting, export — keeps working unchanged.
Transforming source data with shortcode attributes
Most "transform the data" needs are already covered declaratively, with no code, through real [tablecrafter] attributes processed by TC_Data_Fetcher. These run after the source returns its rows:
| Attribute | Required | Transform |
|---|---|---|
| source | Required | The source URL or protocol string that selects the handler. |
| root | Optional | Dot-notation path into nested JSON before rendering, e.g. data.items (extract_from_root()). |
| include | Optional | Comma-separated columns to keep; supports aliasing with key:Label and preserves the listed order (process_columns()). |
| exclude | Optional | Comma-separated columns to drop. |
| sort | Optional | column:direction, e.g. price:desc (sort_data(), numeric-aware). |
// Pull a nested array, rename and reorder columns, sort by price
[tablecrafter source="https://api.example.com/catalog.json" root="data.products"
include="name:Product, price:Price, sku" sort="price:desc"]
Filter: customizing export templates
The export pipeline (includes/class-tc-export-handler.php, which produces genuine XLSX and PDF output) exposes its template list through the tc_export_templates filter. Add or override a template to control metadata inclusion, date format, and number format:
add_filter('tc_export_templates', function (array $templates) {
$templates['invoice'] = [
'name' => 'Invoice',
'description' => 'Currency-formatted invoice export',
'include_metadata' => true,
'date_format' => 'M j, Y',
'number_format' => '$0.00',
];
return $templates;
});
The built-in templates the filter receives are default, business, and data_analysis. The export feature itself is enabled per-table with the export="true" shortcode attribute.
Filter: trusting proxy headers behind a CDN
When TableCrafter rate-limits or logs requests, it resolves the client IP through TC_Security::get_client_ip(). By default no proxy headers are trusted. If your tables sit behind Cloudflare or a load balancer, opt in with the tablecrafter_trusted_ip_headers filter, returning an array of recognized keys (cloudflare, forwarded, real_ip):
add_filter('tablecrafter_trusted_ip_headers', function () {
return ['cloudflare']; // trusts HTTP_CF_CONNECTING_IP only
});
Only enable headers your infrastructure actually sets. Trusting a header a client can spoof lets attackers forge their source IP and bypass rate limiting. Resolved IPs are still validated against private and reserved ranges.
Reacting to cache refresh (cron action)
TableCrafter uses a stale-while-revalidate strategy: a rendered table older than five minutes schedules a background re-fetch via the tc_refresh_single_source action, which receives the table's full attribute array. The plugin hooks its own refresh_source_cache() handler to it, but you can attach your own listener to log, warm dependent caches, or notify on refresh:
add_action('tc_refresh_single_source', function (array $atts) {
error_log('TableCrafter re-fetched: ' . ($atts['source'] ?? ''));
}, 20, 1);
Because the handler receives the same $atts the shortcode used, you have access to source, include, exclude, root, sort, and the refresh settings for that specific table instance.
Client-side: TableCrafter custom events
The frontend script (assets/js/tablecrafter.js) emits bubbling CustomEvents on the table container so you can react to interaction in mobile card view without touching plugin internals. All events are namespaced tablecrafter:<name>:
| Event | Fires when | detail payload |
|---|---|---|
| tablecrafter:cardTap | A card is tapped (expand/collapse) | { rowData, rowIndex, card } |
| tablecrafter:cardView | A card's view action runs | { rowData, rowIndex } |
| tablecrafter:cardEdit | A card's edit action runs | { rowData, rowIndex } |
const table = document.querySelector('.tablecrafter-container');
table.addEventListener('tablecrafter:cardTap', (e) => {
console.log('Row', e.detail.rowIndex, e.detail.rowData);
});
Because the events bubble, you can also delegate from a parent element. The container also exposes its server-rendered seed data in a <script type="application/json" class="tc-initial-data"> tag and its configuration via data-source, data-root, data-include, data-sort, and the data-* refresh attributes.
Theming the table output
Rather than a source-level transform, presentation is customized through real CSS. TableCrafter ships CSS custom properties used by its high-contrast accessibility mode, scoped to .tc-wrapper.tc-high-contrast in assets/css/tablecrafter.css:
| Variable | Default | Controls |
|---|---|---|
| --tc-bg-color | #ffffff | Cell background |
| --tc-text-color | #000000 | Cell text |
| --tc-border-color | #000000 | Table and cell borders |
| --tc-focus-color | #ff0000 | Focus outline |
/* Brand the high-contrast palette */
.tc-wrapper.tc-high-contrast {
--tc-border-color: #1b3a57;
--tc-focus-color: #ffb300;
}
For ordinary styling, target the stable structural classes — .tablecrafter-container, .tc-table, .tc-card, .tc-card-header, .tc-cards-container, .tc-pagination, and .tc-controls — which are emitted on every render.
Summary of real extension points
- Filters:
tc_export_templates,tablecrafter_trusted_ip_headers. - Action:
tc_refresh_single_source(receives the table$atts). - JS events:
tablecrafter:cardTap,tablecrafter:cardView,tablecrafter:cardEdit. - CSS variables:
--tc-bg-color,--tc-text-color,--tc-border-color,--tc-focus-color. - Declarative data transforms:
root,include(with aliasing),exclude,sortattributes. - Source-class pattern: a fork-level convention under
includes/sources/dispatched by URL protocol — not a runtime registration hook.
Next, see shortcode-reference.html for the full attribute list and data-sources-overview.html for how the built-in CSV, Google Sheets, Airtable, and JSON sources are configured.