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.
DraftGuides

Your First Page Template

Create a page template that defines the content regions within a layout using CwaComponentGroup.

Your layout is in place — but the <slot /> is empty. A page template fills it. It's a Vue file that defines the regions for a page: where the hero sits, where the main content goes, where the sidebar lives.

Each region is a CwaComponentGroup. You declare which regions exist and what they're called; editors fill them with components at runtime.

IMAGE: Page template in edit mode showing two CwaComponentGroup regions highlighted as editable areas with + hotspots

The File

Create app/cwa/pages/PrimaryPageTemplate.vue:

<template>
  <div class="mx-auto max-w-7xl px-4 py-10">
    <CwaComponentGroup
      reference="primary"
      :location="props.iri"
    />
  </div>
</template>

<script setup lang="ts">
import type { IriProp } from '#cwa/composables/cwa-resource'

const props = defineProps<IriProp>()
</script>

Key points

defineProps<IriProp>() — every page template receives its page's IRI as a prop. The module passes this automatically when rendering the page.

CwaComponentGroup reference="primary" :location="props.iri" — creates a content region called primary owned by this page. Any components the editor adds here are stored against this specific page record.

The location must be the IRI of a layout, page, or component. For page regions, always use props.iri — the IRI the module passes in automatically for the current page.

Multiple Regions

Real page templates usually have several regions:

<template>
  <div>
    <!-- Full-width hero area -->
    <CwaComponentGroup
      reference="hero"
      :location="props.iri"
      :allowed-components="[CwaComponentNames.HeroSection]"
    />

    <!-- Main content + optional sidebar -->
    <div class="max-w-7xl mx-auto px-4 py-10 flex gap-10">
      <div class="flex-1">
        <CwaComponentGroup
          reference="main"
          :location="props.iri"
        />
      </div>
      <aside class="w-64">
        <CwaComponentGroup
          reference="sidebar"
          :location="props.iri"
        />
      </aside>
    </div>
  </div>
</template>

Each CwaComponentGroup with a unique reference becomes a separate, independently manageable region in the CMS.

VIDEO: In edit mode on a page using this template: hovering the hero, main and sidebar regions to show each has its own +, and the hero's Add Component dialog offering only Hero Section while main and sidebar offer every component type

Register in nuxt.config

// nuxt.config.ts
cwa: {
  pages: {
    PrimaryPageTemplate: {
      name: 'Primary Page',
      classes: {
        'Big Text': 'text-2xl',
      }
    }
  }
}

classes works exactly as it does for layouts — a map of style name to classes, with a Default option added for you. Omit it entirely if this template has no style options.

Create a Page Record

A page template Vue file defines the structure; a Page record in the database gives it a URL and assigns it to a layout.

Naming convention: the page template's global component name is the filename prefixed with CwaPage — PrimaryPageTemplate.vue → CwaPagePrimaryPageTemplate. Unlike layouts, the admin's dropdown stores the short, unprefixed form (PrimaryPageTemplate), while the fixture builder writes the prefixed form. Both resolve to the same component: the module strips and re-applies the prefix when it resolves uiComponent, so don't be alarmed if a page you created in the admin and one you seeded from fixtures show different values.

Via the admin

Go to /_cwa/pages → the + button → choose Primary Page as the Page UI (stored as uiComponent: PrimaryPageTemplate), assign a layout, and click Add Now. The page opens on its Routes tab, where you set the route path.

IMAGE: Admin create page form showing uiComponent, layout, and route fields
VIDEO: Creating a page end to end: /_cwa/pages → + → choosing Primary Page as the Page UI and picking the layout → Add Now; the page opening on its Routes tab, clicking Create New Route, entering the route suffix and clicking Save Route, then visiting the URL to see the layout wrapping the still-empty page

Via fixtures

$cwa->page('home', 'PrimaryPageTemplate', layout: 'main', route: '/');
$cwa->flush();

The 'main' argument refers to the layout reference from your scaffold ($cwa->layout('main', ...)) — not the uiComponent value. See Data Fixtures for the full API.

Template Pages for Dynamic Content

Static pages (home, about, contact) map one-to-one: one URL, one page. But for repeating content — blog articles, events, products — you want the same page structure reused across many URLs. For this, you create a template page:

$cwa->page('blog-template', 'PrimaryPageTemplate', layout: 'main', isTemplate: true);

A template page has no URL. Instead, each blog article (or product, or event) is a PageData record that references this template and gets its own URL. All those records share the same component regions you've defined in PrimaryPageTemplate.vue, but each has its own data.

In the admin, blue records are static pages, yellow are template pages, and green are PageData records (individual articles, products, etc.) — shown orange until they have a route. This colour coding makes it easy to see what's a shared structure vs. an individual piece of content.

The full dynamic pages pattern — how PageData records work, how to bind component positions to data fields — is in Dynamic Pages.

Next: Create a Component

With a layout and page template in place, build a component to fill those regions with real, editable content.