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.

Data Fetcher WordPress Filters Source Protocols JS Events CSS Theming

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:

  1. 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). .json and .csv extensions are parsed.
  2. Airtable — URLs starting with the airtable:// protocol are delegated to TC_Airtable_Source.
  3. CSV / Google Sheets — a Google Sheets docs.google.com/spreadsheets/d/… URL or any URL ending in .csv is delegated to TC_CSV_Source.
  4. 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:

RequirementDetail
Return shapeA flat array of rows, each row an associative array keyed by column name. Header keys come from the first object/CSV row.
Error shapeReturn a WP_Error on failure; TC_Data_Fetcher surfaces an admin error helper for users with manage_options, and a graceful message otherwise.
ProtocolPick 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:

AttributeRequiredTransform
sourceRequiredThe source URL or protocol string that selects the handler.
rootOptionalDot-notation path into nested JSON before rendering, e.g. data.items (extract_from_root()).
includeOptionalComma-separated columns to keep; supports aliasing with key:Label and preserves the listed order (process_columns()).
excludeOptionalComma-separated columns to drop.
sortOptionalcolumn: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>:

EventFires whendetail payload
tablecrafter:cardTapA card is tapped (expand/collapse){ rowData, rowIndex, card }
tablecrafter:cardViewA card's view action runs{ rowData, rowIndex }
tablecrafter:cardEditA 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:

VariableDefaultControls
--tc-bg-color#ffffffCell background
--tc-text-color#000000Cell text
--tc-border-color#000000Table and cell borders
--tc-focus-color#ff0000Focus 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

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.