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.
DraftComponent Helpers

Collections & Pagination

Render paginated resource lists with useCwaComponent and the withCollection() plugin, with page navigation bound to the URL query string.

The Collection built-in component proxies any resource collection endpoint. On the front-end, useCwaComponent with the withCollection() plugin unwraps the Hydra response and gives you items, pagination state, and navigation helpers — all URL-bound.

Display Component

<!-- app/cwa/components/BlogList/BlogList.vue -->
<template>
    <div>
        <div v-if="isLoadingCollection" class="space-y-4">
            <div v-for="n in 6" :key="n" class="h-48 bg-gray-100 animate-pulse rounded" />
        </div>

        <div v-else-if="collectionItems" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
            <ArticleCard
                v-for="item in collectionItems"
                :key="item['@id']"
                :article="item"
                :to="resolveResourceLink(item, 'routePath')"
            />
        </div>

        <div v-if="totalPages > 1" class="flex items-center justify-center gap-4 mt-8">
            <button
                :disabled="!pageModel || pageModel <= 1"
                class="btn"
                @click="goToPreviousPage"
            >
                Previous
            </button>

            <span class="text-sm text-gray-600">
                Page {{ pageModel }} of {{ totalPages }}
            </span>

            <button
                :disabled="pageModel >= totalPages"
                class="btn"
                @click="goToNextPage"
            >
                Next
            </button>
        </div>
    </div>
</template>

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

const props = defineProps<IriProp>()

const {
    resource,
    exposeMeta,
    collectionItems,
    isLoadingCollection,
    totalPages,
    pageModel,
    goToNextPage,
    goToPreviousPage,
    resolveResourceLink
} = useCwaComponent(props, [withCollection()])

defineExpose(exposeMeta)
</script>

Return Values

ReturnTypeDescription
collectionItemsComputedRef<CwaResource[] | undefined>The response's collection.member array — each item is a raw API resource, so read its fields directly (item.title, not item.data.title)
isLoadingCollectionRef<boolean>true while the proxied collection fetch is in progress
totalPagesRef<number>Parsed from the page parameter of the collection's view.last link; 1 when there is no last-page link
pageModelRef<number>Current page number — bound to the URL ?page= query param
goToNextPage() => voidIncrements pageModel
goToPreviousPage() => voidDecrements pageModel (min: 1)
changePage(page: number) => voidJump to a specific page
resolveResourceLink(resource, property) => string | RouteLocationTurn an item into a link target — see below

collectionItems is undefined until the first response arrives — guard your list with v-if="collectionItems".

Numbered Pagination

useCwaCollectionPagination returns a sliding window of page numbers — at most maxPagesToDisplay (default 7), centred on the current page. They are always plain numbers; there are no ellipsis markers.

It takes a single props object of plain numbers, which makes it a natural fit for a dedicated pagination child component:

<!-- app/components/CollectionPagination.vue -->
<template>
    <nav class="flex gap-1">
        <button
            v-for="page of pages"
            :key="page"
            :class="page === currentPage ? 'font-bold' : ''"
            @click="$emit('change', page)"
        >
            {{ page }}
        </button>
    </nav>
</template>

<script setup lang="ts">
import { useCwaCollectionPagination } from '#cwa/composables/cwa-collection-pagination'
import type { CwaPaginationEmits, CwaPaginationProps } from '#cwa/composables/cwa-collection-pagination'

const props = defineProps<CwaPaginationProps>()  // { currentPage, totalPages, maxPagesToDisplay }
defineEmits<CwaPaginationEmits>()                // next, previous, change

const { pages } = useCwaCollectionPagination(props)
</script>

Then drop it into the collection component in place of the Previous/Next buttons:

<CollectionPagination
    :current-page="pageModel || 1"
    :total-pages="totalPages"
    :max-pages-to-display="7"
    @next="goToNextPage"
    @previous="goToPreviousPage"
    @change="changePage"
/>

Linking to a Resource's Page

resolveResourceLink(item, property) — destructured from useCwaComponent(props, [withCollection()]) above — returns item[property] when the item carries its own path (e.g. routePath on a published page-data resource), and otherwise a Vue Router location targeting CWA's internal resource-page route.

<CwaLink :to="resolveResourceLink(item, 'routePath')">{{ item.title }}</CwaLink>
Bind it to :to, not :href — the fallback is a route location object, not a string. Pass the property name too; without it the helper always falls through to the internal route.

This is how you turn a collection item into a link without knowing its URL in advance — including draft entries that have no public route yet.

User-Controlled Sorting and Filtering

useQueryBoundModel binds any query parameter to the URL, triggering a re-fetch when it changes:

import { useQueryBoundModel } from '#imports'

// Pass the bracket prefix — the composable expands it to order[<key>] in both directions
const { model: sortOrder } = useQueryBoundModel('order', { defaultValue: { createdAt: 'desc' } })
// sortOrder.value → { createdAt: 'desc' }; writing it updates ?order[createdAt]=... in the URL
<select
    :value="sortOrder.createdAt"
    @change="sortOrder = { createdAt: ($event.target as HTMLSelectElement).value }"
>
    <option value="desc">Newest first</option>
    <option value="asc">Oldest first</option>
</select>

Assign a new object rather than mutating a key — the model only writes back to the URL when the ref itself is reassigned.

The composable returns { model } — not the ref itself — and its second argument is an options object (defaultValue, delay, asNumber). For a plain scalar parameter, pass the parameter name and v-model the ref directly:

const { model: searchModel } = useQueryBoundModel('title', { delay: 250 })

The collection re-fetches automatically when the URL changes. Default query parameters set in the PHP Collection entity are the baseline; useQueryBoundModel lets users override them.

Admin: Setting Up a Collection

The manager tab (auto-provided by the built-in Collection entity) lets admins:

  1. Select which resource to list (resourceIri — searchable dropdown)
  2. Set perPage — how many items per page
  3. Configure defaultQueryParameters — default sort and filter values

No custom admin tab file needed for the standard Collection component — it's provided by the bundle.