Skip to main content

CMS environments

Each Ovok project gets one Payload tenant (row in tenants). Within that tenant, content is further scoped by environment:

EnvironmentTypical use
devSandbox / integration drafts
stagingPre-release validation
prodLive published content

How isolation works

  • Every tenant-scoped collection (content-types, content-items, posts, …) includes an environment field.
  • Composite unique indexes prevent slug collisions within an environment, not across them: (tenant, environment, slug).
  • ovok-core forwards x-ovok-environment on every CMS proxy call.
  • Writes require a valid environment header; reads default to dev when the header is missing (authoring proxy only — public delivery passes environment explicitly).

Enabling an environment

Environments are enabled per project through the control plane (Console → Settings → Payload CMS, or POST /v1/cms/projects/:slug/environments).

Each enablement:

  1. Records project_environments in control-plane Postgres.
  2. Provisions the Payload tenant if this is the first environment.
  3. Marks the environment active.

Suspending an environment sets status: suspended without deleting content rows.

Public delivery

Published reads must include the target environment as a query parameter:

curl "https://api.sandbox.ovok.com/v1/public/cms/pages/items?environment=dev&projectId=$MEDPLUM_PROJECT_ID" \
-H "Authorization: Bearer $OVOK_CMS_KEY"
ParamRequiredDescription
environmentYesdev, staging, or prod
projectIdYesMedplum project UUID for the CMS tenant

The API key alone identifies the Ovok project; projectId resolves the Payload tenant. Do not use projectSlug on public delivery.

Production write policy

Production read-only enforcement for dashboard writes is handled at the ovok-core proxy, not inside Payload. Payload still scopes data by tenant + environment for all operations.

Next