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="['/component/hero_sections']"
    />

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

Register in nuxt.config

// nuxt.config.ts
cwa: {
  pages: {
    PrimaryPageTemplate: {
      name: 'Primary Page',
      classes: [
        { value: '', label: 'Default' }
      ]
    }
  }
}

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: unlike layouts, page template uiComponent values are not prefixed — the value is the Vue filename as-is. PrimaryPageTemplate.vuePrimaryPageTemplate.

Via the admin

Go to /_cwa/pagesCreate → set uiComponent to PrimaryPageTemplate, assign a layout, and set the route path.

IMAGE: Admin create page form showing uiComponent, layout, and route fields

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