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: {
'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 → 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.