The CWA is in heavy development
The CWA is still in alpha and not ready for production - some code and implementations are likely to change. If you would like to try out the CWA, please enjoy what we have provided and feel free to provide feedback, or get involved on GitHub.
DraftCore Concepts

Draft & Publish Workflow

How admins edit draft versions of components without touching the live site, then publish when ready.

Every publishable resource has two versions: a draft and a published version. The live site always shows the published version. Admins edit only the draft. Nothing goes live until they explicitly publish.

The Problem It Solves

Without a draft system, every edit is immediately visible to all visitors. A content editor halfway through rewriting a section would show a broken state to the public. The draft/publish workflow eliminates this.

How It Works

For anonymous users: the API returns the published version of every resource.

For admins in edit mode: the API returns the draft version. Edits via useCwaResourceModel (the debounced v-model binding) patch the draft.

When an admin publishes: a PATCH to the API promotes the draft to the published version. A Mercure event broadcasts the change to all open browser sessions.

The PHP Side

#[Silverback\Publishable]
#[ApiResource(mercure: true)]
#[ORM\Entity]
class Title extends AbstractComponent
{
    use PublishableTrait;

    #[Assert\NotBlank(groups: ['Title:published'])]
    public ?string $title = null;
}
  • #[Silverback\Publishable] + PublishableTrait — the API bundle manages the draft/published twin automatically
  • publishedAt is set on the published version when it goes live
  • Assert\NotBlank(groups: ['Title:published']) — validation only runs at publish time, not when saving a draft. This lets admins save incomplete work

Validation Groups

The groups: ['Title:published'] constraint pattern is important:

  • Saving a draft: no Title:published validation → empty title is allowed
  • Publishing: runs the Title:published validation group → empty title fails

This prevents publishing invalid content while still allowing in-progress work to be saved.

Real-Time Updates

When the published version changes, Mercure broadcasts the update. The Nuxt module's resource store updates reactively — visitors see the new content without a page refresh. No polling, no stale cache.

Component Groups and Layouts

PublishableTrait applies per-component — the draft/publish distinction lives on each individual component entity. But adding or removing a component from a group (creating or deleting a ComponentPosition) is a separate operation with no draft state of its own.

When an admin adds a new component to a group via the inline editor, a ComponentPosition record is created immediately. The component itself starts as a draft (no publishedAt), so it is invisible to anonymous visitors — but the position exists from that moment. Publishing the component makes it visible; deleting it removes both the component and its position.

There is no "undo" for position changes beyond deleting the position. Layout-level ComponentGroup changes (e.g. adding a nav link to a shared navigation group) are also immediate and do not go through a draft state.