> From [PageWeave Design](https://pageweave-design.pageweave.site) — a working library for agentic web development. Full index: [llms.txt](https://pageweave-design.pageweave.site/llms.txt)

# Tables as CMS

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

- Section: Build
- Tags: tables content data csv import

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:

```liquid
<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.