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.
DraftAdmin Manager

useCwaResourceUpload

Admin tab composable for file upload fields — handles selection, upload, and deletion via the API.

Use in admin tab components for uploadable components. Not for display components.

The composable returns a ready-to-spread bind object — spread it onto CwaUiFormFile with v-bind and it wires the v-model, fileExists, disabled, and the change/delete events for you. Only label (and optionally accept) stay per-field:

import { useCwaResourceManagerTab, useCwaResourceUpload } from '#imports'

const { exposeMeta, iri } = useCwaResourceManagerTab({ name: 'Upload' })
const { bind } = useCwaResourceUpload(iri)

defineExpose(exposeMeta)
<template>
  <CwaUiFormFile v-bind="bind" label="Upload Image" />
</template>
Always destructure bind. It's a ComputedRef, and Vue only auto-unwraps refs that are top-level setup bindings. So v-bind="bind" unwraps correctly, but v-bind="upload.bind" (nested access on the composable-return object) does not — it spreads the raw ref and TypeScript complains that modelValue/fileExists are missing. Destructure const { bind } = useCwaResourceUpload(iri), and for multiple fields destructure-and-rename per call (see below).

Signature

useCwaResourceUpload(
  iri: ComputedRef<string | undefined>,
  filename: string = 'file',
  fileDisplayType: string = 'File'
)
  • iri — the reactive IRI ref from useCwaResourceManagerTab. Identifies which resource to PATCH.
  • filename — the PHP entity property name / media object key for the file field. Defaults to 'file'. Pass the actual property name if your entity uses a different name (e.g. 'image', 'document').
  • fileDisplayType — noun used in the delete-confirmation dialog copy (e.g. 'Image', 'Document'). Defaults to 'File'.

Return values

ReturnTypePurpose
bindComputedRef<object>Spread onto CwaUiFormFile via v-bind — covers v-model, fileExists, disabled, change, delete
filenameInputModelRef<string>Current filename (the v-model target inside bind)
updatingRef<boolean>true while an upload/delete is in progress
fileExistsComputedRef<boolean>true when a file is already uploaded for this resource
handleInputChangeFile(file: File | undefined) => Promise<void>Change handler (also inside bind as onChange)
handleInputDeleteFile() => Promise<void>Delete handler (also inside bind as onDelete)

bind is the recommended path. The individual values remain exported if you need to wire the input manually or drive custom UI.

How it works

handleInputChangeFile — sends the selected file to {iri}/upload as multipart/form-data (not base64). Sets updating.value = true during the upload. On success the resource store updates automatically.

handleInputDeleteFile — shows a confirmation dialog, then sends PATCH {iri} with { [filename]: null }. The API deletes the stored file.

filenameInputModel — bind with v-model on CwaUiFormFile. Shows the current filename when a file exists, empty otherwise.

CwaUiFormFile props

bind supplies the wiring; add label (and optionally accept) per field:

<CwaUiFormFile
  v-bind="bind"          <!-- v-model, fileExists, disabled, change, delete -->
  label="Image"
  accept="image/*"       <!-- optional MIME restriction -->
/>

Complete admin upload tab example

<!-- app/cwa/components/HeroSection/admin/ImageTab.vue -->
<template>
  <CwaUiFormFile v-bind="bind" label="Upload Image" />
</template>

<script setup lang="ts">
import { useCwaResourceManagerTab, useCwaResourceUpload } from '#imports'

const { exposeMeta, iri } = useCwaResourceManagerTab({ name: 'Upload' })
const { bind } = useCwaResourceUpload(iri)

defineExpose(exposeMeta)
</script>

Multiple file fields

There's no multi-file wrapper — call useCwaResourceUpload once per field, passing each field's property name. Because you can't spread a nested field.bind (it won't unwrap — see the callout above), destructure and rename each bind:

<template>
  <CwaUiFormFile v-bind="posterBind" label="Poster" accept="image/*" />
  <CwaUiFormFile v-bind="thumbnailBind" label="Thumbnail" accept="image/*" />
</template>

<script setup lang="ts">
import { useCwaResourceManagerTab, useCwaResourceUpload } from '#imports'

const { exposeMeta, iri } = useCwaResourceManagerTab({ name: 'Media' })
const { bind: posterBind } = useCwaResourceUpload(iri, 'poster')
const { bind: thumbnailBind } = useCwaResourceUpload(iri, 'thumbnail')

defineExpose(exposeMeta)
</script>

Each call reads and writes its own mediaObjects[<property>] key, so two fields on the same resource never couple at the composable level. If fields do appear to share a file, it's a resource-data issue (a lost _metadata.mediaObjects key), not this composable.