Add a field widget
A field widget is the input that the generic form shows for one field. Core has widgets for most field types. A layer adds a widget for richtext or media, or a special widget for one field.
The contract
Every widget has the same three props and one event:
| Name | Kind | Meaning |
|---|---|---|
field | Prop | The field definition. The form passes a translated copy, so the label is ready to show. The copy is made only when the form knows the content type name. Otherwise the field keeps its own text. |
modelValue | Prop | The current value |
disabled | Prop | true when the form is read-only |
update:modelValue | Event | Emit it with the new value |
<script setup lang="ts">
defineProps<{
field: PlutoField
modelValue: unknown
disabled?: boolean
}>()
defineEmits<{ 'update:modelValue': [value: string] }>()
</script>
<template>
<UFormField :label="field.label" :description="field.description">
<UInput
:model-value="String(modelValue ?? '')"
:disabled="disabled"
@update:model-value="$emit('update:modelValue', String($event))"
/>
</UFormField>
</template>
Register the widget
Register the widget with contentFieldWidgets and defineAsyncComponent:
const MyColorField = defineAsyncComponent(() => import('../components/MyColorField.vue'))
export default defineNuxtPlugin(() => {
definePlutoExtension({
id: 'my-layer',
contentFieldWidgets: [
{ id: 'my-color', widget: 'my-color', component: MyColorField }
]
})
})
Core shows a field skeleton while the chunk loads.
Choose by name or by type
An entry can match in two ways:
widget: 'my-color'. A field selects the widget withwidget: 'my-color'. This affects only that field.fieldType: 'media'. The widget becomes the default for every field of that type.
Resolution order: an exact field.widget name first. Then the last registered widget whose fieldType matches the field type. Otherwise nothing, and the form shows the message No widget registered for field type "<type>".
widget name when it is for one layer. A fieldType entry becomes the app-wide default for that type, and it can take the fields of other layers.Core has no richtext widget on purpose. @plutocms/supabase-blog is the default for richtext. Core has the media widget: the picker of the media library. The Git CMS registers a Markdown widget for richtext in CMS mode. The @plutocms/docs layer registers an mdc widget by name. A field selects it with widget: 'mdc'.
Use a widget in a field
{ name: 'color', type: 'text', label: 'Color', widget: 'my-color' }
Share state between widgets
Each widget mounts on its own and cannot read the props of another widget. If a rule spans fields, share the state with useState. supabase-shop does this for the custom, availability, and stock rule. Reset the state when the widgets unmount, so the next form starts clean.
Load your own options
A content field widget is mounted dynamically. An async composable with a top-level await can suspend an already mounted page. Fetch your options in onMounted instead.