For the full documentation index, see PageWeave Documentation or fetch llms.txt.

Data Tables

Tables store structured data per website. Each table has a schema with typed fields (string, text, number, boolean, datetime). Rows are created, updated, and deleted via MCP tools or REST API.

Tables are accessible in three ways:

  1. MCP toolslist_tables, create_table, update_table, delete_table, list_table_rows, create_table_row, update_table_row, delete_table_row
  2. Liquid templatingsite.tables.{slug} in page HTML
  3. Public JSON APIGET /t/{table_id} on the website’s own domain (no auth required)

Managing Tables

Create a Table

create_table(website, name: "Products", display_column_key: "name", fields_schema: { fields: [
  { name: "name", type: "string" },
  { name: "price", type: "number" },
  { name: "description", type: "text" }
]})
  • name — Human-readable name
  • slug — URL-safe identifier (auto-generated from name if omitted)
  • display_column_key — Field used for row slug generation (required)
  • fields_schema{ fields: [{ name, type }] }
    • Types: string, text, number, boolean, datetime

Update a Table

update_table(table_id, name: "New Name")

Only name and display_column_key can be updated. Field schema changes require creating a new table.

List Tables

list_tables(website)          # all tables
list_tables(website, table_id: "...")  # single table

Delete a Table

delete_table(table_id)

Requires confirmation. Permanently removes the table and all rows.

Managing Rows

Create a Row

create_table_row(table_id, data: { name: "Widget", price: 29.99 })
  • data — Key-value pairs matching the table’s field names
  • Row slug is auto-generated from the display_column_key field

List Rows

list_table_rows(table_id)                    # all rows, paginated
list_table_rows(table_id, row_id: "...")     # single row
list_table_rows(table_id, offset: 20, limit: 10)

Update a Row

update_table_row(row_id, data: { price: 19.99 })

Only provided fields change.

Delete a Row

delete_table_row(row_id)

Requires confirmation.

Liquid Templating

Tables are accessible via site.tables.{slug}:

{% for row in site.tables.products %}
  <li>{{ row.name }} — ${{ row.price }}</li>
{% endfor %}

Filtering

{% for row in site.tables.products | where: "category", "electronics" %}
  {{ row.name }}
{% endfor %}

Multiple filters chain together:

{% assign filtered = site.tables.products | where: "category", "electronics" | where: "in_stock", "true" %}

Ordering

{% for row in site.tables.products | sort_by: "price", "asc" %}
  {{ row.name }}: ${{ row.price }}
{% endfor %}

Pagination

{% paginate site.tables.products by 10 order_by: "created_at" order_dir: "desc" %}
  {% for row in site.tables.products %}
    <article>{{ row.name }}</article>
  {% endfor %}
  {% if paginate.next %}<a href="{{ paginate.next.url }}">Next</a>{% endif %}
{% endpaginate %}

Pagination parameters:

  • by — Rows per page (default 20, max 100)
  • order_by — Field name or created_at
  • order_dirasc or desc
  • where — Filter expression ("field:value")

Row Properties

Every row exposes:

  • row.id — UUID
  • row.slug — Auto-generated slug
  • row.created_at — Timestamp
  • row.updated_at — Timestamp
  • row.url — URL to the row’s template page (if configured)
  • Any field from the table schema: row.name, row.price, etc.

Template Pages

Associate a page with a table to create dynamic row URLs. Path must contain :slug; additional :field_name placeholders (e.g. :lang in /blog/:lang/:slug) map to string/text fields in the table schema.

create_page(website, path: "/products/:slug", html: "...", table_slug: "products")
create_page(website, path: "/blog/:lang/:slug", html: "...", table_slug: "blog")

Each row renders at /products/{row-slug}. Template HTML has access to the row variable:

<article>
  <h1>{{ row.name }}</h1>
  <p>${{ row.price }}</p>
  <div>{{ row.description }}</div>
</article>

Public JSON API

Every table serves a public JSON endpoint at GET /t/{table_id} on the website’s own domain. No authentication required. Always-on for all tables.

Endpoint

GET https://{subdomain}.pageweave.site/t/{table_id}
GET https://{custom-domain}/t/{table_id}
  • {table_id} — Table UUID (not slug). Get it via list_tables or the REST API.

Query Parameters

Parameter Description Default
page Page number 1
per Rows per page (max 100) 20
sort Sort field. Prefix with - for descending created_at desc
search Full-text search across string/text fields
filter[field] Exact match on field
filter[field_neq] Not equal
filter[field_gt] Greater than (numeric fields)
filter[field_gte] Greater than or equal
filter[field_lt] Less than (numeric fields)
filter[field_lte] Less than or equal
filter[field_cont] Contains (case-insensitive)
filter[field_start] Starts with (case-insensitive)
filter[field_end] Ends with (case-insensitive)

Examples

Basic listing:

curl "https://my-site.pageweave.site/t/abc123-def456"

Pagination:

curl "https://my-site.pageweave.site/t/abc123-def456?page=2&per=10"

Sorting:

# Ascending by name
curl "https://my-site.pageweave.site/t/abc123-def456?sort=name"

# Descending by price
curl "https://my-site.pageweave.site/t/abc123-def456?sort=-price"

Filtering:

# Exact match
curl "https://my-site.pageweave.site/t/abc123-def456?filter[category]=electronics"

# Numeric range
curl "https://my-site.pageweave.site/t/abc123-def456?filter[price_gte]=10&filter[price_lte]=50"

# Contains (case-insensitive)
curl "https://my-site.pageweave.site/t/abc123-def456?filter[name_cont]=widget"

# Starts with
curl "https://my-site.pageweave.site/t/abc123-def456?filter[name_start]=A"

Search:

curl "https://my-site.pageweave.site/t/abc123-def456?search=blue widget"

Combined:

curl "https://my-site.pageweave.site/t/abc123-def456?page=1&per=20&sort=-price&filter[category]=electronics&filter[price_gte]=10"

Response Shape

{
  "rows": [
    {
      "id": "uuid-1",
      "slug": "widget-a",
      "data": { "name": "Widget A", "price": 29.99, "category": "electronics" },
      "created_at": "2026-06-01T12:00:00Z"
    }
  ],
  "meta": {
    "current_page": 1,
    "next_page": 2,
    "prev_page": null,
    "total_pages": 5,
    "total_count": 47
  }
}

JavaScript Example

const res = await fetch("https://my-site.pageweave.site/t/abc123-def456?filter[category]=electronics&sort=-price");
const { rows, meta } = await res.json();

rows.forEach(row => {
  console.log(`${row.data.name}: $${row.data.price}`);
});

CORS

The API sets Access-Control-Allow-Origin only when the request Origin matches the website’s own origins:

  • The pageweave.site subdomain URL
  • Any configured custom domain

External origins receive no CORS headers.

Rate Limiting

60 GET requests per minute per IP for paths starting with /t/. Returns 429 Too Many Requests when exceeded.

Error Responses

Status Meaning
404 Table not found (invalid UUID or table doesn’t exist)
410 Website is suspended