Introduction
Every website has the same skeleton.
A layout — the persistent outer shell with header, navigation, and footer. Pages that live inside it, each divided into named component groups — regions like "hero", "sidebar", or "navigation". And filling those regions, the actual content components: headings, images, rich text, navigation links.
This structure is so universal that CWA makes it a first-class data model. Layouts, pages, routes, and components aren't configuration files — they're records in a database, managed through an API. That shift changes what becomes possible:
- Content editors can create pages, restructure regions, or publish changes without a deployment or a developer — the structure lives in the database, not code.
- Every open browser session sees those changes instantly — no page reload, no polling. Powered by Mercure, wired up automatically.
- Your clients edit content directly on the page — no separate CMS application, no context switch. Enter edit mode and everything on the page becomes manageable inline. A few admin screens for layouts, routes, users and settings live under
/_cwaon the same site. - Add the
#[Silverback\Publishable]attribute andPublishableTraitto a component type and it gets a full draft/publish lifecycle — role-based access control, publish-time validation, the lot.
Your job as a developer: define what components look like and what data they hold. Routing, the admin CMS, real-time updates, draft/publish workflows, caching — handled.
How it fits together
one PHP entity · one Vue component · one admin tab
You write one PHP entity, one Vue display component, and one admin tab per component type. The module resolves every URL to the right layout, page, and component tree — no routing code, no CMS wiring.
Try it live
Before writing any code, see what you're building towards. The playground is a complete CWA site — try editing content in the inline CMS, publishing a change, and watching it propagate live across browser tabs. The button opens its sign-in page: sign in with the username and password admin, which the page shows too.
The two parts
A CWA application has two sides that communicate through a typed REST API:
The API — a Symfony application with the API Components Bundle installed. You extend AbstractComponent to define content types, add attributes such as #[Silverback\Publishable], #[Silverback\Uploadable] or #[Silverback\Timestamped] (each with its matching trait), and run migrations. The bundle provides authentication, serialisation, Mercure real-time publishing, and all the admin endpoints.
The Nuxt app — a Nuxt application with the CWA module. In the template it lives in app/, with layout files in app/app/cwa/layouts/, page templates in app/app/cwa/pages/, and component Vue files in app/app/cwa/components/. The module handles route resolution, resource fetching, SSR, and the full inline CMS.
The mapping is automatic: the Title PHP class (api/src/Entity/Title.php) renders via app/app/cwa/components/Title/Title.vue. The module finds the Vue file by its location, so there's nothing to wire up. An entry in nuxt.config only adds the name and options the admin shows.
Get started
Scaffold a new project with the interactive CLI — pick your features, optionally wire GitHub Actions or GitLab CI, and get Docker running in one step:
pnpm create cwa my-project
Then read Key Ideas before the guides. It explains the handful of terms the guides use.
Where next
Pick the path that matches what you're here to do.
Check Releases to see which bundle, module and template versions go together.