Your First Page Template
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.
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.
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.vue → PrimaryPageTemplate.
Via the admin
Go to /_cwa/pages → Create → set uiComponent to PrimaryPageTemplate, assign a layout, and set the route path.
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.
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.