---
title: Architecture
description: The apps, the shared packages, and where each CMS ends and the shared front-end code begins.
---

```txt
apps/              Deployable apps: CMS installs, vinext front-ends, the MCP Worker, this docs site
packages/          Shared libraries: content model, UI, adapters, Access helpers, logging, MCP
wordpress-shared/  Patronage's WordPress code that both installs load, and the deploy kit
docs/              Engineering notes: decision records in docs/adr/, research in docs/spikes/
```

## Apps

| App | What it is |
| --- | --- |
| `cms-wordpress-headless` | WordPress in Docker as an editor and a WPGraphQL API. It renders nothing public. |
| `web-wordpress` | The vinext front-end for headless WordPress, deployed as a Worker. |
| `payload` | Payload as one vinext app on Workers: the admin, plus front-end pages that read through the Local API. |
| `cms-wordpress-timber` | A traditional WordPress install with a Timber theme. It shares design tokens with the others, not components. See [Traditional WordPress](/references/timber-wordpress). |
| `mcp` | Launch's deployment of the MCP gateway Worker, behind Access, with its WordPress and Payload connectors. See [MCP server](/references/mcp). |
| `docs-site` | This site. |

## Shared packages

- **`content-model`**: the CMS-neutral shape of a page and its sections.
- **`ui`**: shadcn/ui components on Tailwind 4, the `Section`, `Container` and `Typography` primitives, and one component per content-model block. They take plain props and know nothing about WordPress or Payload. See [Shared UI](/references/ui).
- **`adapter-wordpress`** and **`adapter-payload`**: map each CMS's data to `content-model`. See [CMS-neutral sections](/research/cms-neutral-sections).
- **`access`**: provisions the Access applications, calls the CMS through Access, and verifies Access tokens. See [Lock the CMS behind Cloudflare Access](/research/access-lockdown).
- **`logging`**: the Worker wrapper that prints one wide event per request, and the script that provisions a site's log bucket. WordPress's side is the `patronage-logging` mu-plugin. See [Logging](/references/logging).
- **`sections-preview`**: a parity test and a static page that render the same page from both CMSs side by side.
- **`demo-content`**: the placeholder images and their alt text that every CMS's seed uses.
- **`images`**: serves CMS images through the Cloudflare Images binding, on one same-origin route.
- **`mcp-core`**, **`mcp-wordpress`** and **`mcp-payload`**: the MCP gateway and its two connectors, built for npm under `@patronage`. `apps/mcp` deploys them. See [MCP server](/references/mcp).

## Shared WordPress code

Both WordPress installs load the same Patronage code from `wordpress-shared/`:

- **`patronage-fields`**: the ACF field groups, post types, rich-text shortcodes, the form model and the demo seed.
- **`patronage-access`**: the mu-plugin that verifies Cloudflare Access sign-ins. Only the headless install uses it, since the Timber install serves its public site from WordPress.
- **`patronage-logging`**: the mu-plugin that records WordPress events. See [Logging](/references/logging).
- **`wp-deploy`**: the deploy kit. See [Deploying WordPress](/deploy/wordpress).

## Where the CMS stops

Each CMS has an adapter that maps its data to `content-model`. Rich text and editor field definitions stay CMS-specific: the adapter renders rich text and passes it to the shared components as children. That keeps `ui` free of any one editor's format.

## Locking the CMS down

The pattern every reference follows:

1. The CMS admin and API are behind a Cloudflare Access application.
2. The front-end Worker reaches the CMS with an Access service token, or (for Payload) through the Local API in the same Worker.
3. The CMS tells the front-end what changed through a signed revalidation request, so public pages stay cached until an edit is published.
