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

Images & Media

Display uploaded files with useCwaComponent and withFile(), manage uploads in admin tabs with useCwaResourceUpload, and use Imagine filter variants.

When a PHP component uses #[Silverback\Uploadable], use useCwaComponent with the withFile() plugin. It exposes the uploaded file's URL, load state, and Imagine filter variants under a files map keyed by the field's fileProp (default 'file').

withFile() replaces the removed withImage(). The old plugin returned flat contentUrl/displayMedia/… keys; withFile() nests them under files.<fileProp>. Map entries are reactive, so in templates you use them without .value (files.file.contentUrl).

Display Component

<!-- app/cwa/components/Image/Image.vue -->
<template>
    <div class="relative">
        <Transition name="fade">
            <NuxtImg
                v-if="files.file?.displayMedia"
                ref="file"
                :src="files.file.contentUrl"
                :width="files.file.displayMedia.width"
                :height="files.file.displayMedia.height"
                class="w-full h-full object-cover"
                @load="files.file.handleLoad"
            />
        </Transition>
        <div
            v-if="!files.file?.displayMedia"
            class="w-full h-64 bg-gray-200 animate-pulse rounded"
        />
    </div>
</template>

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

const props = defineProps<IriProp>()

const { resource, exposeMeta, files } = useCwaComponent(props, [withFile()])

defineExpose(exposeMeta)
</script>

The ref="file" matches the default fileProp, so the plugin can detect an already-cached image on mount.

The files Map

Each entry — files[fileProp] — carries these values for that field:

ValueTypeDescription
contentUrlstring | undefinedPublic URL of the (optionally filtered) file
displayMediaMediaFile | undefinedThe media object to display — has contentUrl, mimeType, width, height; undefined until available
loadedbooleanBecomes true when handleLoad() is called
handleLoad() => voidCall on the <img> element's @load event

Because entries are reactive, use them directly in templates (files.file.contentUrl) — no .value. Gate rendering on files.file?.displayMedia so the skeleton shows until the media is resolved and the browser has loaded the image.

Using a Specific Imagine Filter

Pass imagineFilterName to surface a specific image variant as displayMedia/contentUrl:

const { files } = useCwaComponent(props, [
    withFile({ imagineFilterName: 'thumbnail' })
])
// files.file.contentUrl → the 'thumbnail' variant

Without imagineFilterName, the field resolves to the original uploaded file.

Multiple File Fields

A component can hold more than one uploadable field (e.g. a poster and a thumbnail). Add one withFile() per field, each with its own filePropuseCwaComponent accumulates them into the same files map:

const { files } = useCwaComponent(props, [
    withFile({ fileProp: 'poster' }),
    withFile({ fileProp: 'thumbnail', imagineFilterName: 'small' }),
])
// files.poster.contentUrl · files.thumbnail.contentUrl

Each fileProp must match a distinct #[UploadableField] property on the PHP entity. For the standalone, non-plugin form, see useCwaFileField.

Admin Upload Tab

useCwaResourceUpload returns a bind object — spread it onto CwaUiFormFile with v-bind and only supply the label (and optional accept) per field:

<!-- app/cwa/components/Image/admin/Image.vue -->
<template>
    <div class="p-4">
        <CwaUiFormFile v-bind="upload.bind" label="Upload Image" accept="image/*" />
    </div>
</template>

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

defineProps<IriProp>()

const { exposeMeta, iri } = useCwaResourceManagerTab({ name: 'Image', order: 1 })
const upload = useCwaResourceUpload(iri, 'file')  // 'file' = the PHP property name

defineExpose(exposeMeta)
</script>

useCwaResourceUpload(iri, propertyName) handles file selection, multipart upload to {iri}/upload, and deletion via PATCH. For multiple file fields, call it once per property. See useCwaResourceUpload for the full reference.

Video, PDF, and Other Files

The same pattern works for any file type — withFile() is not image-specific. For non-image files there's no @load event to wait for, so render as soon as files.file?.contentUrl is available:

<template>
    <a v-if="files.file?.contentUrl" :href="files.file.contentUrl" download>
        Download PDF
    </a>
</template>

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

const props = defineProps<IriProp>()
const { exposeMeta, files } = useCwaComponent(props, [withFile()])
defineExpose(exposeMeta)
</script>

For video:

<video v-if="files.file?.contentUrl" :src="files.file.contentUrl" controls class="w-full" />

Transition While Loading

<style>
.fade-enter-active, .fade-leave-active { transition: opacity 0.3s; }
.fade-enter-from, .fade-leave-to { opacity: 0; }
</style>

The <Transition name="fade"> wrapping the <NuxtImg> produces a smooth fade-in once the image loads.