CWA 2 is a prerelease (2.0.0-alpha), heading for a beta. It runs production sites; configuration can still change between releases, always with upgrade notes. View versions and support
Configuration

Nuxt Config

[object Object]

All CWA configuration lives under the cwa: key in nuxt.config.ts. The module merges your config with its defaults at build time.

Runtime Config

The API URLs are set via Nuxt's runtimeConfig so they can be overridden per-environment using environment variables:

// nuxt.config.ts
export default defineNuxtConfig({
    runtimeConfig: {
        cwa: {
            apiUrl: 'http://api-internal'               // server-side (SSR), never sent to the browser
        },
        public: {
            cwa: {
                apiUrlBrowser: 'https://api.example.com' // client-side
            }
        }
    }
})

In Docker Compose these typically differ — the server-side URL uses the internal Docker network hostname while the browser URL is the public domain.

Override at deploy time via environment variables:

NUXT_CWA_API_URL=http://api:8080
NUXT_PUBLIC_CWA_API_URL_BROWSER=https://api.example.com

The server reads the private cwa.apiUrl first. public.cwa.apiUrl (NUXT_PUBLIC_CWA_API_URL) still works as a fallback, but it publishes the internal URL in every page, so the server logs a deprecation warning when it uses it. In the browser, apiUrlBrowser is used, falling back to public.cwa.apiUrl.

Either URL may point at a bare host (https://api.example.com, http://api:8000) or include a path prefix (https://example.com/_api) — both are supported. A trailing slash is trimmed, so https://example.com/_api/ works too.

Full cwa: Reference

export default defineNuxtConfig({
    cwa: {
        // Register your custom components for the admin "Add Component" dialog
        resources: {
            Title: {
                name: 'Title Block',
                description: 'A headline or section title',
                instantAdd: false,      // true = skip config dialog, add immediately
                defaultData: {          // pre-fill fields on creation
                    title: 'New Title'
                }
                // Note: component style options are NOT declared here — see below
            }
        },

        // Register your layout components for the admin panel
        layouts: {
            Primary: {
                name: 'Primary Layout',
                // Style name → the classes it applies. A "Default" (no classes)
                // option is added for you.
                classes: {
                    'Light': 'bg-white text-gray-900',
                    'Dark': 'bg-gray-900 text-white'
                }
            }
        },

        // Register your page template components
        pages: {
            Primary: {
                name: 'Primary Page',
                classes: {
                    'Big Text': 'text-2xl'
                }
            },
            BlogDetail: {
                name: 'Blog Article'
            }
        },

        // Register PageData types for the admin data management panel
        pageData: {
            BlogArticleData: {
                name: 'Blog Articles',
                // Human-readable labels for pageDataProperty position pickers.
                // Falls back to auto-split camelCase → Title Case if omitted.
                properties: {
                    htmlContent: 'Article Body',
                    heroImage: 'Hero Image',
                },
                metaFields: [
                    {
                        field: 'status',
                        type: 'select',
                        label: 'Status',
                        options: [
                            { label: 'Draft', value: 'draft' },
                            { label: 'Live', value: 'live' }
                        ]
                    }
                ]
            }
        },

        // How many levels deep to resolve nested page routes (default: 4)
        pagesDepth: 2,

        // Static defaults for site config — merged with API values, API wins on conflict
        siteConfig: {
            siteName: 'My App',
            canonicalUrl: 'https://www.example.com',
            fallbackTitle: true,
            concatTitle: true,
            indexable: true,
            sitemapEnabled: true
        },

        // Resize large images in the browser before an admin upload sends them.
        // These are the defaults. See "Image Uploads" below.
        upload: {
            image: {
                enabled: true,
                thresholdEdge: 2560,
                thresholdPixels: 20_000_000,
                maxEdge: 2560,
                maxPixels: 20_000_000,
                quality: 0.85
            }
        },

        // Shared-cache headers for rendered page HTML. See Page Caching.
        pageCache: {
            enabled: true,
            sharedMaxAge: 3600,       // optional cap; unset by default, so pages follow the API's s-maxage
            staleWhileRevalidate: 0
        },

        // Shared-cache headers for the CWA sitemap source (/__sitemap__/cwa-urls)
        sitemapCache: {
            sharedMaxAge: 600,
            staleWhileRevalidate: 0
        },

        // Routes kept for instant revisits (default: 200, 0 = no limit)
        routeCacheLimit: 200,

        // Set only to override detection of isr/swr/prerender route rules
        staticRender: false,

        // Service-worker caches cleared when a session ends (needs @vite-pwa/nuxt)
        auth: {
            clearCachesOnSessionEnd: ['cwa-api']
        },

        // Co-editing: other admins' saves appear in place for a signed-in admin (default: true)
        realtime: {
            enabled: true
        },

        // Override the Nuxt layout applied to every page that doesn't set its own (default: 'cwa-root-layout')
        layoutName: 'cwa-root-layout'
    }
})
classes is a map, not a list. The key is the style name shown in the admin dropdown and the value is the class string it applies (a string[] is also accepted and joined). It exists on layouts and pages only — not on resources. Component style variants are declared in the Vue file via useCwaComponent(props, [], { styles: { classes } }); see Alternative UI Variants.
type accepts 'input' (a text field) or 'select' (a dropdown). options is required for 'select' and ignored for 'input'.

Config Key Reference

KeyTypeDefaultDescription
resourcesRecord<string, CwaResourceMeta>built-in ComponentPosition and ComponentGroup entriesYour CMS component types and their admin metadata. Every component in app/app/cwa/components/ gets an entry automatically (name from the component name, plus its admin/ tabs and ui/ variants); your config is merged over it
layoutsRecord<string, CwaUiMeta>{}Your layout component types and admin display options
pagesRecord<string, CwaUiMeta>{}Your page template component types
pageDataRecord<string, { name?, properties?, metaFields? }>{}Your PageData resource classes for the admin data panel. properties maps PHP property names to human-readable labels used in position pickers; auto-splits camelCase if omitted.
pagesDepthnumber4Maximum nesting depth for nested page routes
routeCacheLimitnumber200Max routes kept in the instant-revisit cache. 0 disables eviction (unbounded, only sensible for small sites). See Route Cache
staticRenderbooleanauto-detectedSet when the app serves prerendered/ISR/SWR HTML, so payload-hydrated resources are re-fetched on mount. Auto-detected from routeRules at build; set explicitly only to override
pageCache{ enabled?, sharedMaxAge?, staleWhileRevalidate? }{ enabled: true, staleWhileRevalidate: 0 }Shared-cache headers for rendered page HTML. sharedMaxAge is unset by default, so pages follow the API's s-maxage. See Page Caching
sitemapCache{ sharedMaxAge?, staleWhileRevalidate? }{ sharedMaxAge: 600, staleWhileRevalidate: 0 }Shared-cache headers for the module's sitemap source /__sitemap__/cwa-urls and the custom sitemap
auth.clearCachesOnSessionEndstring[]['cwa-api'] when @vite-pwa/nuxt is installed and not disabledCache Storage caches the browser deletes on sign-out or on a 401 while signed in. Without @vite-pwa/nuxt nothing is cleared. See Clearing the cache when a session ends
realtime.enabledbooleantrueCo-editing for signed-in admins: another admin's save appears in place, with Use theirs / Keep mine on a field you're changing, the admin bar shows who else is here, and admin writes send X-CWA-Client-Id. false gives admins the outdated notice instead, stops that header and presence, and makes useCwaRealtime send nothing. See Real-Time Updates › Co-Editing in the Admin.
upload.imagePartial<ImageDownscaleOptions>{ enabled: true, thresholdEdge: 2560, thresholdPixels: 20000000, maxEdge: 2560, maxPixels: 20000000, quality: 0.85 }How admin uploads resize large images in the browser. See Image Uploads
siteConfigPartial<SiteConfigParams>(defaults)Static site config defaults merged with the API
layoutNamestring'cwa-root-layout'Nuxt layout name used on CWA-managed content pages, and on any of your own pages that don't set a layout
storeNamestring'cwa'Pinia store name prefix (rarely needs changing)

Route Cache

The module keeps the resources of the last pages visited in memory, so going back to one of them shows it at once while it is fetched again in the background. routeCacheLimit is how many routes it keeps. When there are more, the least recently visited route is dropped, with its resources that no other kept route, the page on screen or a fetch in progress still uses.

The default is 200 routes. Looking a resource up and dropping a route don't slow down as more is stored, so a navigation stays fast however many routes are kept. Memory is the limit: about 3 KB for each stored resource, so roughly 100 MB at 30,000 resources. The module keeps the cache in memory only, and there are no plans for a tier that moves it elsewhere. Lower the limit if your pages hold many resources each, and set 0 to keep every route only on a small site.

Image Uploads

When an admin uploads a JPEG, PNG or WebP through useCwaResourceUpload, the browser resizes it first if it is large. By default, an image over 2560 px on its longest edge or over 20 megapixels is resized to fit both. cwa.upload.image changes this for the whole app. You only need to set the options you change:

// nuxt.config.ts
export default defineNuxtConfig({
    cwa: {
        upload: {
            image: {
                enabled: false // upload every image unchanged
            }
        }
    }
})

A field can override these with its own imageDownscale option. The field's value wins, then this config, then the built-in default. useCwaResourceUpload › Large Images Are Resized Before Upload explains each option and what resizing keeps.

Keep these under the API's upload limits. The template's API refuses images over 20 MB or 30 megapixels. If you raise maxEdge or maxPixels, check the API still accepts the result. See Large Images and PHP Memory.

definePageMeta CWA Options

These are all the CWA keys a page in app/app/pages/ can set in definePageMeta. A page of your own uses either disabled (optionally with staticLayout) or fetch:

definePageMeta({
    cwa: {
        // Don't resolve this path as CWA content. Required on every page of
        // your own that isn't CWA content (see below).
        disabled: true,

        // Render this layout component instead of the one resolved from the API's
        // Layout resource. A registered component name (app/app/cwa/layouts/primary.vue
        // registers as CwaLayoutPrimary), rendered inside cwa-root-layout.
        // It is not a Nuxt layout name.
        staticLayout: 'CwaLayoutPrimary',
    }
})
definePageMeta({
    cwa: {
        // Render a CWA page by IRI instead of the Route for this path.
        // Don't combine it with `disabled`, which would stop this fetch too.
        fetch: {
            iri: '/_/pages/<page id>',
            manifestPath: '/_/resource_manifest/<page id>', // optional
        },
    }
})
KeyTypeDescription
disabledbooleanSkip CWA route resolution: no Route, manifest or resources are fetched. Auth state and useCwa() still work
staticLayoutstringComponent name of a fixed layout, used instead of the Layout resource's uiComponent
fetch.iristringIRI of the page or page data to load as the primary resource instead of the path's Route
fetch.manifestPathstringOptional manifest path, so the page's resources are fetched in one batch instead of as they're found

The rule for disabled: set cwa: { disabled: true } on every page of your own that isn't CWA content, including overrides of the layer's auth pages. The CWA route middleware runs on every navigation. Without the flag it asks the API for a Route at the page's path, and when there isn't one the visitor gets the error page. The only page of your own that leaves it off is one that uses fetch, because disabled stops every fetch, including that one.

The Nuxt layout key is separate from these. Pages that don't set it get cwa-root-layout (or your layoutName).

Mixing Your Own Pages with CWA shows each key in use, including loading a CWA page by IRI.