Journey Docs
CMS

Page Types

Partner and loyalty program join pages, the draft/published/archived lifecycle, clone-on-edit, member gating, routing, and environment promotion

A CMS page carries exactly one page_type. The type is fixed in code, not registry data, because it changes which modules are available and how the page is reached.

page_typePurpose
partnerGeneral partner and campaign pages — the default
loyalty_program_joinThe join page for a specific loyalty program

A loyalty_program_join page is bound to its program through core.cms_loyalty_program_join_pages, keyed by program_id (the partner's custom loyalty program external_id) with a unique page_group_id. One cusotm loyalty program has at most one join page, and deleting the program cascades.

Module availability by type

The page type gates part of the palette:

Block typepartnerloyalty_program_join
program_join_formunavailableavailable
signupavailableunavailable
member_auth_actionavailableunavailable
everything elseavailableavailable

A join page owns its own join form, so the generic signup and member-auth modules would be redundant and ambiguous there.

Lifecycle

state is one of draft, published, or archived.

Pages are grouped by a ULID page_group_id rather than a single row. A page group holds at most one row per state, enforced by idx_cms_pages_group_state_unique.

Clone-on-edit

Editing a published page does not mutate it. A draft row is cloned from the published row and edited in isolation; publishing promotes the draft transactionally. This is why slug uniqueness is (slug, state) rather than a flat unique on slug — a draft intentionally shares its slug with the still-live published sibling it will replace.

Cloning preserves layout nodeKey values and remaps internal IDs, so layout and block identity survive the round trip and in-flight layout changes cannot leak into the live page.

Member gating

Two columns on core.cms_pages control access:

ColumnMeaning
member_auth_requiredthe page is member-gated
member_gate_program_idthe program a turned-away visitor is sent to join

member_gate_program_id overrides owner attribution; NULL falls back to it. It is an internal id — resolution emits the program's external_id.

The gate program is resolved from the route's owner rather than from the page's ownership record, so it does not depend on the caller having requested the entityOwner hydrate. A client that omitted that hydrate would otherwise silently receive null and bounce its visitors to a bare sign-in screen.

Member-gated pages are always noindex and nofollow — see SEO details.

Routing

core.cms_page_routes maps a page group to a path, an owner, and optionally a custom domain.

ConstraintEffect
unique page_group_idone route per page group
unique (owner_entity_type, owner_entity_id, path)no duplicate path within an owner
unique (custom_domain_id, path) where domain is not nullno duplicate path on a custom domain

Every owner also gets one fallback host in core.cms_fallback_hosts, created automatically the first time a route is made for that owner and reused thereafter. Its label is derived from the owner's external id, so the hostname is always {owner external id}.sites.journey.com — a working URL before any custom domain exists. The suffix is CMS_SITE_HOST_SUFFIX; the label is normalized to a valid 63-character DNS label rather than chosen by an author.

Resolution produces both hostnames and picks a canonical one:

canonicalHostname = customHostname ?? fallbackHostname

A custom domain only contributes once its status is active; a page group whose custom domain is pending or removed continues to serve on the fallback host. Deleting a custom domain sets the route's custom_domain_id to NULL rather than removing the route.

Environment promotion

Pages and block schemas are promoted between environments rather than authored separately in each. The promotion logic lives in api/src/core/cms/pages/promotion/, driven by two scripts:

ScriptPromotes
api/scripts/promote-cms-pages.tspage groups with their layout, blocks, theme, and SEO
api/scripts/promote-cms-block-schemas.tsblock-type schemas

Block schemas promote first — a page cannot land in an environment whose registry lacks its module types.

Storage

TableHolds
core.cms_pagesslug, internal name, type, state, member gate
core.cms_page_ownershiporganization, brand, or property owner
core.cms_page_routespath, owner, optional custom domain
core.cms_fallback_hostsone host label per owner
core.cms_loyalty_program_join_pagesprogram to page-group binding
core.cms_page_blocksmodule instances and published compile snapshots

Source: api/src/core/cms/pages/schema/, api/src/core/cms/pages/promotion/.

On this page