Shared data & publishing
From Airtable reference to a living website catalogue.
It started with…
Communications needs to publish a searchable catalogue and programme pages from the business reference, while retaining control over what visitors can see.
So we built…
A Supabase publishing copy and a Next.js website on Vercel. Selected source fields feed the catalogue; editorial statuses, persisted media and public queries shape the visitor experience.
- Airtable
Maintain
Business model and relationships
- Supabase
Prepare for publishing
Selected fields, statuses and media
- Vercel
Serve the website
Next.js reads the publishable catalogue
This documented flow uses a dedicated synchronisation engine. Zapier can coordinate other events; it does not replace that engine in this case.
Take a look inside
Screen 1 of 22. Explore the public catalogue
The website offers search and filters over the synchronised reference.
What we set out to change
Keep the public offer current while giving communications an explicit publication decision.
How it is builtData model, code, controls and team ownership
The user journey
- 01
Synchronise selected source fields into the publishing copy.
- 02
Review editorial content and approve publication.
- 03
Serve the searchable catalogue and linked programme pages.
Data and hand-off structure
| Entity / table | Key fields | Relationships |
|---|---|---|
| Web catalogue | airtable_id · status · slug | Supabase publishing copy |
| Versions & sync runs | State · log · version | Publishing history |
Technical implementation
- Fields are mapped explicitly; records are upserted by stable airtable_id.
- Required website assets are persisted to Storage instead of relying on temporary attachment URLs.
- New content starts as draft. Disappearing records are marked, with a check on large catalogue changes.
Engineering decision
Keep repeatable writes bounded and publication decisions separate
Adapted from the catalogue’s chunked upsert and source-owned field mapping. Stable Airtable IDs match existing rows; editorial publication fields are omitted. Errors stop later chunks and propagate to the run report. Already committed chunks remain committed; repeating the same mapping can converge again, but this is not an all-or-nothing catalogue transaction.
import type { SupabaseClient } from "@supabase/supabase-js";type Programme = { sourceId: string; name: string; slug: string;};export async function syncProgrammes(db: SupabaseClient, source: Programme[]) { // Only source-owned fields belong in this mapping. // is_published, editorial content and SEO overrides stay out. const rows = source.map(row => ({ airtable_id: row.sourceId, name: row.name, slug: row.slug, })); let written = 0; for (let offset = 0; offset < rows.length; offset += 500) { const chunk = rows.slice(offset, offset + 500); const { error, count } = await db.from("programmes").upsert(chunk, { onConflict: "airtable_id", count: "exact", }); if (error) { throw new Error(`Catalogue sync stopped after ${written} rows`); } written += count ?? chunk.length; } return { written };}// Database prerequisites: unique airtable_id, new rows default to draft.// Validate slugs and resolve collisions before this writer runs.Adapted implementation excerpt
Controls and boundaries
- The browser receives no Airtable token or privileged Supabase key.
- Internal fields and staff data are outside the public catalogue.
- Synchronisation is not publication: permissions, statuses and publishing filters have distinct roles.
Who can contribute
- Operations: maintain programmes, campuses and Airtable links.
- Communications: enrich and approve publication in the web back office.
- Developers: evolve mappings, checks and versioned components.
What supports this case
Synchronisation source and publishing model reviewed; the public catalogue and programme page were opened. Screenshots show the actual journey with brands masked.