PageWeave/Design

Guides / Build / Tables as CMS

8 Build tablescontentdatacsvimport .md

Tables as CMS

The table-driven content pattern: schema design, CSV imports, template pages, and draft workflows.

Tables turn PageWeave into a structured CMS. Content lives in rows; pages render it. Never hand-maintain text in two places.

Schema design

One table per content type. Common base fields:

  • slug — URL segment (you supply it; nothing auto-generates)
  • title, subtitle, description — display + meta
  • order — number, position within section
  • tags — space-separated string for filtering
  • content — text, the canonical markdown body

Add fields only when a template needs them (code, preview_html, palette, section). Every field you add is a field every row must carry.

Template pages bind it together

A page at /library/:category/:slug maps each placeholder to a row field. With placeholder_defaults: {"lang": "en"}, rows with lang=en serve at the collapsed URL. The template renders:

<h1>{{ row.title }}</h1>
<div class="prose">{{ row.content | markdownify }}</div>

And the markdown body serves {{ row.content }} raw — the .md twin for free.

Bulk import workflow

  1. Build CSV locally (Python's csv module handles escaping).
  2. Upload as an asset (request_upload_url → PUT → asset URL).
  3. import_table_rows(table_id, source_url) — ≤25k rows syncs inline.
  4. Round-trip with export_table_csv; upsert mode matches on the id column.

The reserved published_at column controls state: empty keeps current, -/draft unpublishes, now publishes, ISO 8601 schedules.

Draft workflow

Draft rows render ONLY on dev environments' template pages — perfect for staging content. Review on *.env.pageweave.site, then flip published_at: now.

Limits & scale

5k rows free / 100k Pro. Liquid loops stay cheap to ~2k rows; beyond that, use the public JSON API (GET /t/{table_id}) with client-side fetch. Template pages themselves scale fine at any size.