Data Model
All authoring state — every site, template, page, theme, version, lock, activity entry, publish request and backup record, plus platform-level records — lives in one DynamoDB table, Content_Store. This is a deliberate single-table design: related items share a partition so they can be fetched together with a single Query, and access patterns are served by the composite key plus two GSIs.
Table shape
Content_Store (see components/content-store.json):
- Primary key: PK (partition, string) + SK (sort, string)
- GSI1: GSI1PK / GSI1SK
- GSI2: GSI2PK / GSI2SK
- Billing: PAY_PER_REQUEST (on-demand)
- Point-in-time recovery: enabled
On-demand billing means capacity scales automatically with traffic; PITR provides continuous backups at the table level (separate from the application-level backup/restore feature).
Partitioning strategy
Most records for a site share the partition SITE#{siteId}, so a single Query on that partition (optionally filtered with begins_with(SK, …)) returns exactly the slice needed — all templates, all pages, a specific sub-type, etc. Platform-wide records use their own partitions.
Per-site items — PK = SITE#{siteId}
SK pattern | Item | Notes |
|---|---|---|
METADATA | Site record | Display name, domain, settings, theme overrides, robots/AI-crawler flags |
THEME | Site theme | Base theme selection + inline theme JSON |
BREAKPOINTS | Site breakpoints | Responsive breakpoint overrides |
TEMPLATE#{templateId} | Page template | Layout regions and defaults |
PAGE#{pageId} | Page | Slug, template ref, hierarchy, zones (component tree), SEO meta, publish status |
SITEGROUP#{groupType} | Site access group | Members granted a per-site role |
LOCK#{pageId} | Edit lock | Held while a page is being edited |
ACTIVITY#{timestamp}#{uuid} | Activity-log entry | Time-ordered audit trail |
PUBLISHREQ#{requestId} | Publish request | Draft-approval workflow record |
BACKUP#{backupId} | Backup record | Status/step metadata for a site or platform backup |
Page versions — PK = SITE#{siteId}#PAGE#{pageId}
SK pattern | Item |
|---|---|
VERSION#{versionNumber} | An immutable snapshot of a page, written on each save/publish so history can be listed and restored |
Version history lives in its own partition (keyed by site + page) so listing or restoring versions never scans the main site partition.
Platform-wide items
PK | SK | Item |
|---|---|---|
VARIABLE#{name} | METADATA | Design variable (design token) in the Variable Registry |
COMPONENT#{componentId} | METADATA | Component definition (metadata for a bundle in Component_Store) |
PAGETYPE#{pageTypeId} | METADATA | Page type available when creating templates |
PLATFORM | BREAKPOINTS | Platform-default responsive breakpoints |
Global secondary indexes
The GSIs serve lookups that don't start from a known PK/SK:
- GSI1 — domain and slug lookup.
- Site records project GSI1PK = DOMAIN#{domain} / GSI1SK = METADATA, so an incoming host can be resolved to its site with a single query (used when routing/serving).
- Pages project GSI1PK = SITE#{siteId} / GSI1SK = SLUG#{slug}, so a page can be found by its slug within a site (e.g. the homepage SLUG#/, the error page SLUG#__error).
- Component definitions project GSI1PK = COMPONENT so the whole component catalogue can be listed without a table scan.
- GSI2 — reserved for additional access patterns (declared on the table for forward-compatibility).
Access-pattern examples
Need | Operation |
|---|---|
List a site's templates | Query PK = SITE#{siteId} AND begins_with(SK, 'TEMPLATE#') |
List a site's pages | Query PK = SITE#{siteId} AND begins_with(SK, 'PAGE#') |
Load one page | GetItem PK = SITE#{siteId}, SK = PAGE#{pageId} |
Resolve a host to a site | Query GSI1 where GSI1PK = DOMAIN#{host} |
Find a page by slug | Query GSI1 where GSI1PK = SITE#{siteId} AND GSI1SK = SLUG#{slug} |
List page versions | Query PK = SITE#{siteId}#PAGE#{pageId} AND begins_with(SK, 'VERSION#') |
List a site's backups | Query PK = SITE#{siteId} AND begins_with(SK, 'BACKUP#') |
List all backups (platform) | Scan filtered by begins_with(SK, 'BACKUP#') |
List the component catalogue | Query GSI1 where GSI1PK = COMPONENT |
Notes on large items
DynamoDB caps a single item at 400 KB. Bulk data that could exceed this is kept out of individual items:
- Rendered HTML and site output go to Publish_Store (S3), not DynamoDB.
- Media/fonts go to Asset_Store (S3).
- During backups, gathered site content is staged in Backup_Store (S3) rather than accumulated on the backup record, so backups work for sites of any size (see Rendering & publishing).
Continue to API reference »