The Data Model: Rows & Columns

TableCrafter treats every data source as a list of records: each record becomes a row, and the keys of the first record become the columns. This page explains exactly how a source is mapped, how headers are generated, and how to point at nested data.

JSON CSV / Google Sheets Airtable Nested data Column control

The core rule: a list of objects

Regardless of where the data comes from, TableCrafter normalizes it into a single shape before building the table: an array where every element is an object (an associative array of key => value pairs). That mapping is fixed:

So a source like this:

[
  { "name": "Acme Co", "plan": "Pro", "seats": 12 },
  { "name": "Globex", "plan": "Team", "seats": 5 }
]

renders a three-column table (name, plan, seats) with two rows. The minimal shortcode is just the source URL:

[tablecrafter source="https://example.com/customers.json"]
ℹ️

Columns are discovered from the first record only. If later records contain extra keys, those keys will not become columns. Make sure your first row is representative, or use the include attribute to declare the columns explicitly.

How each source type maps to rows

TableCrafter accepts several source formats. All of them are converted to the same "list of objects" model described above.

SourceHow it becomes rows & columns
JSON arrayEach array element is a row; first element's keys are the columns. Used as-is.
JSON object (nested)Not a table on its own. Use the root attribute to point at the array inside it (see "Flat vs. nested" below).
CSV file (.csv)Row 1 is parsed as the header line; every following line becomes a record keyed by those headers. A leading UTF-8 BOM on the first header is stripped.
Google Sheets URLA docs.google.com/spreadsheets/d/<id> link is auto-rewritten to its CSV export (preserving the gid tab), then parsed like a CSV.
AirtableEach Airtable record is flattened: its fields object is lifted to the top level, and an _airtable_id column is added.
⚠️

CSV rows whose column count does not match the header row are silently skipped. Ragged rows (missing or extra commas) will not appear in the table, so keep your CSV rectangular.

How headers (column labels) are generated

The raw key becomes the column label after a light formatting pass: underscores are replaced with spaces and the result is title-cased. So first_name renders as the header First Name and seats renders as Seats.

Each header cell is output as a sortable, keyboard-focusable element:

<th class="tc-sortable" tabindex="0" aria-sort="none" data-field="first_name">First Name</th>

The original key is preserved in data-field so sorting, filtering, and value lookup always operate on the real key rather than the display label. Body cells also carry the label in data-tc-label, which the responsive card/stacked view uses to label each value on small screens.

Renaming a header (aliasing)

To override the auto-generated label, use a key:Alias pair inside the include attribute. The part before the colon must match the real key; the part after is shown as the header.

[tablecrafter source="…/customers.json"
  include="name:Company, plan:Subscription, seats:Licenses"]

This both selects the columns and relabels them, producing headers Company, Subscription, and Licenses.

Choosing which columns appear

By default every key in the first record becomes a column. Two attributes let you narrow that set. They are applied in order: include first (which also sets column order), then exclude.

AttributeRequiredBehavior
includeOptionalComma-separated list of keys to keep. Columns render in the order you list them. Supports key:Alias for renaming.
excludeOptionalComma-separated list of keys to drop. Applied after include.
sortOptionalInitial server-side sort in column:direction form, e.g. seats:desc. Numeric values sort numerically; everything else sorts case-insensitively.

Example: show only three fields, in a specific order, sorted by seat count, hiding an internal id:

[tablecrafter source="…/customers.json"
  include="name, plan, seats"
  exclude="internal_id"
  sort="seats:desc"]
💡

Using include is the most reliable way to guarantee a stable column set. Because columns are otherwise inferred from the first record, declaring them explicitly protects you from sources whose first row is missing optional fields.

Flat vs. nested data: the root attribute

TableCrafter can only turn an array into rows. Many JSON APIs wrap their list inside an envelope object, for example:

{
  "status": "ok",
  "results": {
    "customers": [
      { "name": "Acme Co", "seats": 12 }
    ]
  }
}

Point at the nested list with a dot-delimited path in root. TableCrafter walks each segment in turn before mapping rows:

[tablecrafter source="…/api.json" root="results.customers"]

If any segment of the path does not exist, the table fails with a clear "Key not found in data structure" error (shown only to administrators), so a wrong path is easy to spot. The same dot-path logic runs identically on the server (first render) and in the browser (auto-refresh), so a single root value covers both.

Shape at the resolved pathResult
Array of objectsStandard table. Each object is a row.
Array of scalars (strings/numbers)Auto-wrapped into a single Value column so the list still renders.
A single objectTreated as a one-record list (one row).
A scalar or emptyReturns a "not a table / empty dataset" error.

Nested values inside a cell

A column value does not have to be a simple string. When a cell value is itself an array or object, TableCrafter flattens it for display rather than dropping it:

For Airtable specifically, nested field values are flattened at the source: attachment arrays collapse to the first file URL, linked-record arrays join their record ids with commas, and other arrays join with commas. The result is a clean, single-value cell.

ℹ️

Cell rendering is security-hardened: every value is HTML-escaped, image and link URLs are validated before they are turned into <img> or <a> tags, and ISO dates (YYYY-MM-DD) are wrapped in a <time> element.

Where this happens, and verifying it

You configure all of the above through the shortcode (or the matching Gutenberg block / Elementor widget). To work hands-on, open WP Admin → TableCrafter to build a source, then drop the generated [tablecrafter] shortcode into any post or page.

When something does not map the way you expect, administrators see a TableCrafter Setup Guide error block in place of the table. Its troubleshooting checklist mirrors this page: confirm the source returns a list, check the root path, and confirm the data is a list of objects (rows) rather than a single value.

Next steps

Once your data is mapping cleanly to rows and columns, continue with data-sources.html for the full details of connecting JSON, CSV, Google Sheets, and Airtable, or jump to shortcode-reference.html for every attribute the [tablecrafter] shortcode accepts.