From redaxo-mform
Builds REDAXO module input forms with MForm: factory(), addTextField/addSelectField/addRadioField/etc., wrappers (fieldset, tab, accordion, conditional), setters (setLabel, setOptions), and show() rendering. For PHP INPUT code.
npx claudepluginhub friendsofredaxo/claude-marketplace --plugin redaxo-mformThis skill uses the workspace's default tool permissions.
MForm renders the backend input form of REDAXO modules. The PHP code is placed in the **INPUT** section of a module.
Guides Next.js Cache Components and Partial Prerendering (PPR): 'use cache' directives, cacheLife(), cacheTag(), revalidateTag() for caching, invalidation, static/dynamic optimization. Auto-activates on cacheComponents: true.
Processes PDFs: extracts text/tables/images, merges/splits/rotates pages, adds watermarks, creates/fills forms, encrypts/decrypts, OCRs scans. Activates on PDF mentions or output requests.
Share bugs, ideas, or general feedback.
MForm renders the backend input form of REDAXO modules. The PHP code is placed in the INPUT section of a module.
use FriendsOfRedaxo\MForm;
$mform = MForm::factory();
$mform->addTextField(1, ['label' => 'Titel']);
$mform->addTextAreaField(2, ['label' => 'Text']);
echo $mform->show();
MForm field IDs come in three notations, depending on where the field lives:
| Context | ID notation | Stored as | Read with |
|---|---|---|---|
| Direct module field (top level) | integer 1–20 | REX_VALUE[n] | REX_VALUE[n] |
| Inside a repeater row | string key ('title', 'link') | array key in the row JSON | $row['title'] after decoding |
Template (MForm::fromTemplate() / applyTemplate()) | string key ('title', 'seo.title') | resolved when the template is applied — at top level the runtime maps the string to a REX_VALUE slot, inside a repeater it becomes an array key | depends on context |
For most plain modules use integer IDs 1–20 — that's the simple, direct mapping to REX_VALUE[n]. Use string keys only when you write a repeater inner form or a reusable template.
The repeater inner sub-form is described in the mform-flex-repeater skill; the template mechanism is documented further down on this page.
$mform->addTextField(1, ['label' => 'Titel', 'placeholder' => 'Text…']);
$mform->addTextAreaField(2, ['label' => 'Text', 'rows' => 6]);
$mform->addHiddenField(3, 'fixed-value');
$mform->addTextReadOnlyField(4, 'Read-only text');
$mform->addTextAreaReadOnlyField(5, 'Read-only area');
// Typed inputs (renders <input type="…">)
$mform->addInputField('email', 6, ['label' => 'E-Mail']);
$mform->addInputField('url', 7, ['label' => 'URL']);
$mform->addInputField('date', 8, ['label' => 'Datum']);
$mform->addInputField('number', 9, ['label' => 'Zahl', 'min' => 0, 'max' => 100]);
$mform->addSelectField(1, ['' => 'Bitte wählen…', 'a' => 'Option A', 'b' => 'Option B'], ['label' => 'Auswahl']);
$mform->addMultiSelectField(2, ['a' => 'A', 'b' => 'B', 'c' => 'C'], ['label' => 'Multi']); // optional 4th param: $size (default 3)
$mform->addRadioField(3, ['yes' => 'Ja', 'no' => 'Nein'], ['label' => 'Option'], 'yes');
$mform->addCheckboxField(4, [1 => 'Aktiviert'], ['label' => 'Status']);
$mform->addToggleCheckboxField(5, [1 => 'Sichtbar'], ['label' => 'Sichtbarkeit']);
$mform->addCheckboxGroupField(6, ['news' => 'News', 'blog' => 'Blog', 'event' => 'Event'], ['label' => 'Kategorien']);
// Optgroups: use nested array (key = group label)
$mform->addSelectField(8, [
'Typ A' => ['a1' => 'Option A1', 'a2' => 'Option A2'],
'Typ B' => ['b1' => 'Option B1', 'b2' => 'Option B2'],
], ['label' => 'Gruppenauswahl']);
// SQL-based options (query must return two columns: value + label)
$mform->addSelectField(7, null, ['label' => 'Artikel']);
$mform->setSqlOptions('SELECT id AS id, name AS name FROM rex_article WHERE status=1 ORDER BY name');
For link, media, imagelist, and custom-link picker fields see the mform-widgets skill.
// Image-based radio (provides visual layout picker)
$options = [];
for ($i = 1; $i <= 3; $i++) {
$options[$i] = ['img' => rex_url::addonAssets('my_addon', "img/layout$i.svg"), 'label' => "Layout $i"];
}
$mform->addRadioImgField(1, $options, ['label' => 'Layout']);
// Icon-based radio
$mform->addRadioIconField(2, [
'left' => ['icon' => 'fa fa-align-left', 'label' => 'Links'],
'center' => ['icon' => 'fa fa-align-center', 'label' => 'Mitte'],
'right' => ['icon' => 'fa fa-align-right', 'label' => 'Rechts'],
], ['label' => 'Ausrichtung']);
// Color radio
$mform->addRadioColorField(3, [
'white' => ['color' => '#ffffff', 'label' => 'Weiß'],
'black' => ['color' => '#000000', 'label' => 'Schwarz'],
'trans' => ['color' => 'transparent', 'label' => 'Transparent'],
], ['label' => 'Farbe']);
$mform->addHeadline('Abschnitt A');
$mform->addDescription('Hinweistext für den Redakteur.');
$mform->addHtml('<hr>');
$mform->addAlertInfo('Hinweis: Diese Einstellung gilt global.');
$mform->addAlertWarning('Achtung: Nicht leer lassen!');
$mform->addAlertDanger('Fehler – Pflichtfeld.'); // alias: addAlertError()
$mform->addAlertSuccess('Konfiguration gespeichert.');
$mform->addColumnElement(6, MForm::factory()
->addTextField(1, ['label' => 'Spalte 1'])
);
$mform->addColumnElement(6, MForm::factory()
->addTextField(2, ['label' => 'Spalte 2'])
);
$mform->addFieldsetArea('Einstellungen', MForm::factory()
->addTextField(3, ['label' => 'Name'])
->addSelectField(4, ['de' => 'Deutsch', 'en' => 'Englisch'], ['label' => 'Sprache'])
);
$mform->addTabElement('Tab 1', MForm::factory()
->addTextField(1, ['label' => 'Inhalt Tab 1'])
, true); // true = open by default
$mform->addTabElement('Tab 2', MForm::factory()
->addTextAreaField(2, ['label' => 'Inhalt Tab 2'])
);
$mform->addCollapseElement('Erweiterte Einstellungen', MForm::factory()
->addTextField(5, ['label' => 'CSS-Klasse'])
, false, false); // openCollapse=false, hideToggleLinks=false
// Accordion (only one open at a time)
$mform->addAccordionElement('Abschnitt A', MForm::factory()->addTextField(6));
$mform->addAccordionElement('Abschnitt B', MForm::factory()->addTextField(7));
Places multiple fields side-by-side in a single row without the Bootstrap column wrapper. The first parameter is an optional shared label; pass an empty string if you don't need one.
$mform->addInlineElement('Name', MForm::factory()
->addTextField(1, ['label' => 'Vorname'])
->addTextField(2, ['label' => 'Nachname'])
);
$mform->addModalElement(
'Erweiterte Einstellungen', // button label + modal title
MForm::factory()
->addTextField(8, ['label' => 'CSS-Klasse'])
->addSelectField(9, ['sm' => 'Klein', 'lg' => 'Groß'], ['label' => 'Größe']),
'btn-default', // button class
'left' // alignment: left|center|right
);
$mform->addSelectField(1, ['img' => 'Bild', 'video' => 'Video'], ['label' => 'Medientyp']);
$mform->addConditionalFieldsetArea(
1, // source field ID
'=', // operator: = / == | != | > | < | contains | in | empty | !empty
'img', // compare value (for 'in': comma-separated list)
'Bild-Einstellungen',
MForm::factory()->addImagelistField(2, ['label' => 'Bilder'])
);
// Optional last param $action = 'hide' inverts the behaviour (hide when condition is true)
$mform->addConditionalFieldsetArea(1, '=', 'video', 'Video-URL',
MForm::factory()->addTextField(3, ['label' => 'YouTube-URL'])
);
$mform->addTextField(1)
->setLabel('Titel')
->setPlaceholder('Bitte eingeben…')
->setDefaultValue('Standard')
->setFull() // full-width (no label column)
->setFormItemColClass('col-sm-8') // override Bootstrap col for input
->setLabelColClass('col-sm-4') // override Bootstrap col for label
->setTooltipInfo('Hilfetext', 'fa-info-circle')
->setAttribute('data-custom', 'value')
->setAttributes(['class' => 'my-class', 'maxlength' => 255]);
$mform->addSelectField(2, ['a' => 'A', 'b' => 'B'])
->setMultiple()
->setSize(5)
->setDisableOptions(['b']);
$inner = MForm::factory()->addTextField(3, ['label' => 'Sub-Feld']);
$mform->addForm($inner);
echo $mform->show(); // renders and outputs the form
$html = $mform->show(); // or capture as string
Templates encapsulate recurring field groups in a class and register them by key. They are defined once in project/boot.php and applied in any module.
Place template classes in project/lib/MFormTemplate/ (namespace FriendsOfRedaxo\Project\MFormTemplate):
<?php
namespace FriendsOfRedaxo\Project\MFormTemplate;
use FriendsOfRedaxo\MForm;
use FriendsOfRedaxo\MFormTemplate\TemplateInterface as MFormTemplateInterface;
// Optional: project-side interface extends core interface
interface TemplateInterface extends MFormTemplateInterface {}
<?php
namespace FriendsOfRedaxo\Project\MFormTemplate;
use FriendsOfRedaxo\MForm;
final class SeoDefaultsTemplate implements TemplateInterface
{
public function apply(MForm $form, array $context = []): MForm
{
return $form
->addTextField('seo.title', ['label' => 'SEO-Titel'])
->addTextAreaField('seo.description', ['label' => 'Meta-Beschreibung'])
->addMediaField('seo.image', ['label' => 'OG-Bild']);
}
}
$context is passed through from the call site and lets you parametrise the template (e.g. ['variant' => 'dark']).
use FriendsOfRedaxo\MForm;
use FriendsOfRedaxo\Project\MFormTemplate\SeoDefaultsTemplate;
use FriendsOfRedaxo\Project\MFormTemplate\CardDefaultsTemplate;
MForm::registerTemplate('seo_defaults', SeoDefaultsTemplate::class);
MForm::registerTemplate('card_defaults', CardDefaultsTemplate::class);
MForm::fromTemplate() – start a new form from a template:
use FriendsOfRedaxo\MForm;
echo MForm::fromTemplate('seo_defaults')
->addTextField('title', ['label' => 'Seitentitel'])
->show();
->applyTemplate() – merge a template into an existing form (chainable):
use FriendsOfRedaxo\MForm;
$mform = MForm::factory()
->addTextField('headline', ['label' => 'Überschrift'])
->applyTemplate('card_defaults', ['variant' => 'dark'])
->addTextAreaField('body', ['label' => 'Text']);
echo $mform->show();
Inside a repeater:
use FriendsOfRedaxo\MForm;
$itemForm = MForm::fromTemplate('card_defaults')
->addTextField('title', ['label' => 'Titel'])
->addMediaField('image', ['label' => 'Bild']);
echo MForm::factory()
->addRepeaterElement(1, $itemForm, true, true, ['btn_text' => 'Karte hinzufügen'])
->show();
Multiple templates can be chained:
->applyTemplate('base')->applyTemplate('seo'). Without a registered key the form is returned unchanged.
'title', 'link', …), not dotted IDs. See the mform-flex-repeater skill for the full Repeater API.addRepeaterElement() is just an alias for addFlexRepeaterElement() – both exist for backwards compatibility.setSqlOptions() must follow immediately after the addSelectField() call it refers to.setDefaultValue() only applies if the value slot is empty (first render). Existing saved values take precedence.addToggleCheckboxField() stores 1 when checked, empty string when not. Check with '1' === REX_VALUE[n] in OUTPUT PHP.addCheckboxGroupField() stores a comma-separated string. Split with array_filter(explode(',', $val)).redaxo:// links, decoding repeater JSON) is described in the mform-output skill.