Skip to content
DocsDashboard

Tables (table)

Rows and columns of data: orders, availability, price comparisons. Display-only unless select is set.

When to use it: You show rows of the same kind side by side: orders, availability, a price comparison. It only displays data unless you add select, which lets the person pick one or several rows.

When not to use it: Do not use it for a chart-like comparison (use chart) or for a few options to choose from with photos and prices (use the matching picker).

Gather first:

  • The rows and which columns matter to the person.
  • Whether they only read it, or pick rows (select).

Writing good content:

  • Use typed columns (money with a currency, percent, date, status) so values format and sort correctly.
  • Keep to the 8 columns that matter; make key columns sortable and add search above about 15 rows.
  • Without select the table is display-only and returns no answer; add select with a prompt when you need the person to pick rows.

Display-only when select is omitted: in that case the page shows information only, has no Submit button and accepts no answer. Otherwise the person answers as below.

Fields

Every page also has version (always 1), title, and optionally description, branding, cover (a header photo) and expiresAt; see Page fields. These are the fields of page (the page type and its data):

FieldDescription
columnsobject[], required. The columns, in display order (1-8). Limits: 1-8 items.
columns[].idstring, required. Column id, used as the key in every row's values, e.g. "total". Unique. Limits: matches ^[A-Za-z0-9_-]{1,64}$.
columns[].labelstring, required. Header text, e.g. "Total". Required. Limits: 1-40 characters.
columns[].type"text" | "number" | "money" | "percent" | "date" | "datetime" | "status" | "link" | "image", required. Cell type, which fixes the value shape: text = string; number, money (major units, e.g. 24.5) and percent (12.5 means 12.5%) = number; date = "2026-11-03"; datetime = ISO 8601 with offset; status = { label, tone: "neutral" | "good" | "warn" | "bad" }; link = { url (https), label? }; image = { url (https thumbnail), alt? }.
columns[].align"start" | "center" | "end". Optional. Defaults to "end" for number, money and percent, and "start" for everything else.
columns[].formatobject. Optional number formatting. Money columns need { currency }.
columns[].format.currencystring. ISO 4217 code for a money column, e.g. "USD". Required when type is "money".
columns[].format.decimalsinteger. Optional fixed number of decimals for number, money and percent. Limits: 0-4.
columns[].priority"high" | "normal". Optional. On phones each row becomes a card: "high" columns are shown large at the top, the rest as label/value pairs below. Mark the 1-3 columns a person scans for. Defaults to the first non-image column only.
columns[].sortableboolean. Optional. true lets the person sort by this column.
rowsobject[], required. The rows, in default order (0-200; at least 1 with select). An empty list shows an empty state. Limits: at most 200 items.
rows[].idstring, required. Row id. It is returned in the result when the person selects this row. Unique. Limits: matches ^[A-Za-z0-9_-]{1,64}$.
rows[].valuesobject, required. Cell values keyed by column id, e.g. { "item": "Desk lamp", "total": 49 }. Omit a key for an empty cell.
captionstring. Optional table name read by screen readers and shown above it, e.g. "Orders since March". Limits: 1-120 characters.
summaryRowobject. Optional totals row at the bottom, e.g. { "values": { "total": 412.5 } }. It is never sorted or filtered.
summaryRow.labelstring. Optional label in the first column. Defaults to "Total". Limits: 1-40 characters.
summaryRow.valuesobject, required. Cell values keyed by column id, e.g. { "item": "Desk lamp", "total": 49 }. Omit a key for an empty cell.
sortobject. Optional initial sort. Without it rows keep the order you sent.
sort.columnIdstring, required. A sortable column id. Limits: matches ^[A-Za-z0-9_-]{1,64}$.
sort.direction"asc" | "desc", required.
searchboolean. Optional. true shows a search box that filters rows as the person types. Useful above ~15 rows.
groupBystring. Optional column id (text or status) whose value groups the rows under headings. Limits: matches ^[A-Za-z0-9_-]{1,64}$.
selectobject. Optional. Makes rows selectable and adds a Submit button; the result is { rowIds }. Without it the table only shows data.
select.mode"single" | "multiple", required. "single" for one row, "multiple" for several.
select.maxinteger. Optional, multiple only: the most rows the person may pick. Limits: 1-200.
select.promptstring. Optional instruction above the table, e.g. "Pick the orders to return". Limits: 1-120 characters.

Why these are required: A table needs typed columns and rows with ids; values is keyed by column id and each value must match its column's type (an empty {} is an empty row). Display-only unless select is set.

When optional fields are left out: No caption; no totals row; rows keep the order sent; no search box; no groups; display-only (no Submit, every result rejected). Numbers, money and percent align right, the rest left. Money columns need format.currency. Without any priority: "high", phone cards lead with the first non-image column and list the rest as label/value pairs. Columns are not sortable unless sortable: true. Empty cells show a dash. With select, the result is { rowIds } in spec order; single allows one row, multiple up to max (default all).

Minimal example

Only the required fields:

{
  "version": 1,
  "title": "Guests confirmed so far",
  "page": {
    "type": "table",
    "columns": [
      {
        "id": "name",
        "label": "Name",
        "type": "text"
      }
    ],
    "rows": [
      {
        "id": "g1",
        "values": {
          "name": "Priya Shah"
        }
      },
      {
        "id": "g2",
        "values": {
          "name": "Tomás Ortega"
        }
      }
    ]
  }
}

Full example

{
  "version": 1,
  "title": "Which orders should I return?",
  "description": "Returns are free until Nov 30. Pick up to three.",
  "page": {
    "type": "table",
    "caption": "Returnable orders",
    "columns": [
      {
        "id": "item",
        "label": "Item",
        "type": "text",
        "priority": "high",
        "sortable": true
      },
      {
        "id": "ordered",
        "label": "Ordered",
        "type": "date",
        "sortable": true
      },
      {
        "id": "status",
        "label": "Status",
        "type": "status",
        "priority": "high"
      },
      {
        "id": "qty",
        "label": "Qty",
        "type": "number"
      },
      {
        "id": "total",
        "label": "Total",
        "type": "money",
        "format": {
          "currency": "USD"
        },
        "priority": "high",
        "sortable": true
      },
      {
        "id": "tracking",
        "label": "Tracking",
        "type": "link"
      }
    ],
    "rows": [
      {
        "id": "o-1042",
        "values": {
          "item": "Linen duvet cover, queen",
          "ordered": "2026-09-28",
          "status": {
            "label": "Out for delivery",
            "tone": "warn"
          },
          "qty": 1,
          "total": 189,
          "tracking": {
            "url": "https://track.example.test/1042",
            "label": "LX1042"
          }
        }
      },
      {
        "id": "o-1037",
        "values": {
          "item": "Stoneware mugs (set of 4)",
          "ordered": "2026-09-14",
          "status": {
            "label": "Delivered",
            "tone": "good"
          },
          "qty": 2,
          "total": 96,
          "tracking": {
            "url": "https://track.example.test/1037",
            "label": "LX1037"
          }
        }
      },
      {
        "id": "o-1029",
        "values": {
          "item": "Oak bedside table",
          "ordered": "2026-08-30",
          "status": {
            "label": "Delivered",
            "tone": "good"
          },
          "qty": 1,
          "total": 245.5,
          "tracking": {
            "url": "https://track.example.test/1029",
            "label": "LX1029"
          }
        }
      }
    ],
    "select": {
      "mode": "multiple",
      "max": 3,
      "prompt": "Select the orders to return"
    }
  }
}

Result

When the person submits, the result has these fields:

FieldDescription
rowIdsstring[], required. ids of the selected rows, in the order the spec lists them. Limits: 1-200 items.

For the full example above, a result could be:

{
  "rowIds": [
    "o-1042",
    "o-1029"
  ]
}

Send it

eve, AI SDK and MCP

With @pagesystems/eve, @pagesystems/ai-sdk or the MCP server, the model calls get_page_type_schema with "table" (the same JSON as GET /v1/page-types/table), then create_page with this input (no version or branding; branding comes from your dashboard):

{
  "title": "Which orders should I return?",
  "description": "Returns are free until Nov 30. Pick up to three.",
  "page": {
    "type": "table",
    "caption": "Returnable orders",
    "columns": [
      {
        "id": "item",
        "label": "Item",
        "type": "text",
        "priority": "high",
        "sortable": true
      },
      {
        "id": "ordered",
        "label": "Ordered",
        "type": "date",
        "sortable": true
      },
      {
        "id": "status",
        "label": "Status",
        "type": "status",
        "priority": "high"
      },
      {
        "id": "qty",
        "label": "Qty",
        "type": "number"
      },
      {
        "id": "total",
        "label": "Total",
        "type": "money",
        "format": {
          "currency": "USD"
        },
        "priority": "high",
        "sortable": true
      },
      {
        "id": "tracking",
        "label": "Tracking",
        "type": "link"
      }
    ],
    "rows": [
      {
        "id": "o-1042",
        "values": {
          "item": "Linen duvet cover, queen",
          "ordered": "2026-09-28",
          "status": {
            "label": "Out for delivery",
            "tone": "warn"
          },
          "qty": 1,
          "total": 189,
          "tracking": {
            "url": "https://track.example.test/1042",
            "label": "LX1042"
          }
        }
      },
      {
        "id": "o-1037",
        "values": {
          "item": "Stoneware mugs (set of 4)",
          "ordered": "2026-09-14",
          "status": {
            "label": "Delivered",
            "tone": "good"
          },
          "qty": 2,
          "total": 96,
          "tracking": {
            "url": "https://track.example.test/1037",
            "label": "LX1037"
          }
        }
      },
      {
        "id": "o-1029",
        "values": {
          "item": "Oak bedside table",
          "ordered": "2026-08-30",
          "status": {
            "label": "Delivered",
            "tone": "good"
          },
          "qty": 1,
          "total": 245.5,
          "tracking": {
            "url": "https://track.example.test/1029",
            "label": "LX1029"
          }
        }
      }
    ],
    "select": {
      "mode": "multiple",
      "max": 3,
      "prompt": "Select the orders to return"
    }
  }
}

REST

curl https://pagesystems.ai/v1/pages \
  -H "Authorization: Bearer $PAGE_SYSTEMS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"spec":{"version":1,"title":"Which orders should I return?","description":"Returns are free until Nov 30. Pick up to three.","page":{"type":"table","caption":"Returnable orders","columns":[{"id":"item","label":"Item","type":"text","priority":"high","sortable":true},{"id":"ordered","label":"Ordered","type":"date","sortable":true},{"id":"status","label":"Status","type":"status","priority":"high"},{"id":"qty","label":"Qty","type":"number"},{"id":"total","label":"Total","type":"money","format":{"currency":"USD"},"priority":"high","sortable":true},{"id":"tracking","label":"Tracking","type":"link"}],"rows":[{"id":"o-1042","values":{"item":"Linen duvet cover, queen","ordered":"2026-09-28","status":{"label":"Out for delivery","tone":"warn"},"qty":1,"total":189,"tracking":{"url":"https://track.example.test/1042","label":"LX1042"}}},{"id":"o-1037","values":{"item":"Stoneware mugs (set of 4)","ordered":"2026-09-14","status":{"label":"Delivered","tone":"good"},"qty":2,"total":96,"tracking":{"url":"https://track.example.test/1037","label":"LX1037"}}},{"id":"o-1029","values":{"item":"Oak bedside table","ordered":"2026-08-30","status":{"label":"Delivered","tone":"good"},"qty":1,"total":245.5,"tracking":{"url":"https://track.example.test/1029","label":"LX1029"}}}],"select":{"mode":"multiple","max":3,"prompt":"Select the orders to return"}}}}'

Validation errors

A spec that does not validate is refused with 400 invalid_spec. The message names each problem and its path, so a model can fix the spec and try again. For example:

Leaving out the required field columns:

✖ Invalid input: expected array, received undefined
  → at page.columns

Misspelling a field name (unknown keys are rejected):

✖ Unrecognized key: "colour"
  → at page