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: {
        '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 CwaPagePrimaryPageTemplate.vueCwaPagePrimaryPageTemplate. 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/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.