The new Volto Form Block, Plone Conference 2025

robgietema 7 views 18 slides Oct 17, 2025
Slide 1
Slide 1 of 18
Slide 1
1
Slide 2
2
Slide 3
3
Slide 4
4
Slide 5
5
Slide 6
6
Slide 7
7
Slide 8
8
Slide 9
9
Slide 10
10
Slide 11
11
Slide 12
12
Slide 13
13
Slide 14
14
Slide 15
15
Slide 16
16
Slide 17
17
Slide 18
18

About This Presentation

Talk about the new Volto Form Block.


Slide Content

NEW VOLTO FORM BLOCK
Yet another form library?
Plone Conference 2025, Jyväskylä
- Rob Gietema@robgietema

WHY NEW VOLTO FORM BLOCK?
volto-form-block uses custom widgets
require greater customizibility

STEPS TAKEN
Reuse the backend with some minor changes
Use the schema widget
Extend the schema widget / form component

EXAMPLE

FILTER FACTORIES
filterFactory: [
'label_text_field',
'label_choice_field',
'label_boolean_field',
'label_date_field',
'label_datetime_field',
'File Upload',
'label_email',
'radio_group',
'checkbox_group',
'hidden',
'static_text',
'number',
'textarea',
'time',

ADDITIONAL FACTORIES
additionalFactory: [
{ value: 'textarea', label: 'Textarea' },
{ value: 'radio_group', label: 'Radio Group' },
{ value: 'checkbox_group', label: 'Checkbox Group' },
{ value: 'hidden', label: 'Hidden' },
{ value: 'static_text', label: 'Static Text' },
{ value: 'number', label: 'Number' },
{ value: 'time', label: 'Time' },
],

FILTER FIELDS TO BE SEND
filterFactorySend: [
...config.blocks.blocksConfig.schemaForm.filterFactorySend,
'dataprotectionInfo',
],

SIDEBAR

CONDITIONAL SIDEBAR FIELDS
export const schemaFormBlockSchema = ({ intl, ...props }) => {
let data = props.data || props.formData;
let conditional_required = [];
if (data?.store !== true && data?.send !== true) {
conditional_required.push('store');
conditional_required.push('send');
}
if (data?.send_confirmation) {
conditional_required.push('confirmation_recipients');
}
return {
title: intl.formatMessage(messages.form),
fieldsets: [
{

PROVIDED WRAPPERS WITH
LOGIC
widgets: {
...config.widgets,
widget: {
...config.widgets.widget,
textarea: TextareaWrapper,
file: FileWrapper,
date: DatetimeWrapper,
time: TimeWrapper,
datetime: DatetimeWrapper,
email: EmailWrapper,
radio_group: RadioGroupWrapper,
checkbox_group: CheckboxGroupWrapper,
hidden: HiddenWrapper,
},
choices:SelectWrapper,

USE "DUMB" CUSTOM WIDGETS
innerWidgets: {
text: TextField,
textarea: TextAreaField,
file: FileSelector,
date: DatePicker,
datetime: DatePicker,
time: TimeField,
email: TextField,
radioGroup: RadioGroup,
radioGroupOption: Radio,
number: TextField,
checkbox: CheckboxField,
checkboxGroup: CheckboxGroup,
checkboxGroupOption: Checkbox,
select:Select,

ADD FIELD SCHEMA

REGISTER FIELD SCHEMA
config.registerUtility({
name: 'Rich Text',
type: 'fieldFactoryProperties',
method: (intl) => ({
maxLength: {
type: 'integer',
title: intl.formatMessage(messages.maxLength),
},
default: {
title: intl.formatMessage(messages.defaultValue),
widget: 'richtext',
type: 'string',
},
}),
});

INITIAL DATA
config.registerUtility({
name: 'country',
type: 'fieldFactoryInitialData',
method: (intl) => ({
type: 'string',
factory: 'country',
choices: countries[intl.locale].map((country) => [country,
id: 'country',
}),
});

CUSTOM BUTTONS
const ButtonWrapper = ({ primary, secondary, type, title, onCl
return (
<Button
type={type === 'submit' ? 'submit' : 'button'}
styles={secondary ? 'btn btn-secondary' : 'btn btn-prima
onPress={onClick || (() => {})}
label={title}
/>
);
};

CUSTOM FORM COMPONENT
component: FormComponent,

THAT'S ALL FOLKS!
Talk to me about Nick!
slideshare.net/robgietema/volto-form-block