npx claudepluginhub friendsofredaxo/claude-marketplace --plugin redaxo-yformThis skill uses the workspace's default tool permissions.
YForm extends REDAXO with custom database tables that get a backend UI (Table Manager) plus an ORM-style dataset API. A "table" in YForm = a real DB table + a configuration of fields/validators/relations stored in `rex_yform_table` and `rex_yform_field`.
Guides Payload CMS config (payload.config.ts), collections, fields, hooks, access control, APIs. Debugs validation errors, security, relationships, queries, transactions, hook behavior.
Builds production-ready Apache Airflow DAGs with patterns for operators, sensors, testing, and deployment. For data pipelines, workflow orchestration, and batch jobs.
Share bugs, ideas, or general feedback.
YForm extends REDAXO with custom database tables that get a backend UI (Table Manager) plus an ORM-style dataset API. A "table" in YForm = a real DB table + a configuration of fields/validators/relations stored in rex_yform_table and rex_yform_field.
install.php of an addon – idempotent, safe to commit, runs on every install/update.For production code, always use option 2.
| Option | Description |
|---|---|
prio | Sort position among tables in the menu |
table_name | MySQL table name (must include rex_ prefix) |
name | Backend menu label |
description | Documentation text shown to backend editors |
status | 1 = show in menu, 0 = hidden |
list_amount | Pagination – records per page (default 30) |
list_sortfield / list_sortorder | Default sort column and ASC / DESC |
search | Enable backend search field |
hidden | Hide from menu but keep API access |
export | Show export button |
import | Show import button |
history | Enable record versioning |
install.php<?php
// install.php
$table = rex_yform_manager_table::get(rex::getTable('team_member'));
// 1. Ensure the underlying SQL table exists with the right columns
rex_sql_table::get(rex::getTable('team_member'))
->ensurePrimaryIdColumn()
->ensureColumn(new rex_sql_column('name', 'varchar(255)', false, ''))
->ensureColumn(new rex_sql_column('email', 'varchar(255)', false, ''))
->ensureColumn(new rex_sql_column('role', 'varchar(64)', false, ''))
->ensureColumn(new rex_sql_column('photo', 'text', true))
->ensureColumn(new rex_sql_column('bio', 'text', true))
->ensureColumn(new rex_sql_column('status', 'tinyint(1)', false, '1'))
->ensureColumn(new rex_sql_column('createdate', 'datetime', false))
->ensureColumn(new rex_sql_column('updatedate', 'datetime', false))
->ensure();
// 2. Configure YForm metadata for the table
rex_yform_manager_table_api::setTable([
'table_name' => rex::getTable('team_member'),
'name' => 'Team members',
'list_amount' => 30,
'prio' => 1,
'hidden' => 0,
'status' => 1,
'export' => 1,
'import' => 1,
]);
// 3. Define fields (one entry per column the editor sees in the backend)
rex_yform_manager_table_api::setTableField(rex::getTable('team_member'), [
'type_id' => 'value',
'type_name' => 'text',
'name' => 'name',
'label' => 'Name',
'prio' => 1,
]);
rex_yform_manager_table_api::setTableField(rex::getTable('team_member'), [
'type_id' => 'value',
'type_name' => 'text',
'name' => 'email',
'label' => 'Email',
'prio' => 2,
]);
rex_yform_manager_table_api::setTableField(rex::getTable('team_member'), [
'type_id' => 'value',
'type_name' => 'be_manager_relation',
'name' => 'role',
'label' => 'Role',
'table' => rex::getTable('team_role'),
'field' => 'name',
'type' => '0', // 0=single select, 1=multi select, 2=popup, 3=popup multi, 4=inline 1:n, 5=inline m:n
'prio' => 3,
]);
rex_yform_manager_table_api::setTableField(rex::getTable('team_member'), [
'type_id' => 'value',
'type_name' => 'be_media',
'name' => 'photo',
'label' => 'Photo',
'prio' => 4,
]);
rex_yform_manager_table_api::setTableField(rex::getTable('team_member'), [
'type_id' => 'value',
'type_name' => 'textarea',
'name' => 'bio',
'label' => 'Bio',
'prio' => 5,
]);
// 4. Validation
rex_yform_manager_table_api::setTableField(rex::getTable('team_member'), [
'type_id' => 'validate',
'type_name' => 'empty',
'name' => 'name',
'message' => 'Please enter a name.',
'prio' => 100,
]);
rex_yform_manager_table_api::setTableField(rex::getTable('team_member'), [
'type_id' => 'validate',
'type_name' => 'type',
'name' => 'email',
'type_name_internal' => 'email',
'message' => 'Please enter a valid email address.',
'prio' => 101,
]);
update.phpAlways gate each schema change with a version check so reinstalls and partial upgrades stay correct.
<?php
// update.php
$installed = rex_addon::get('my_addon')->getVersion();
if (rex_version::compare($installed, '1.1.0', '<')) {
rex_sql_table::get(rex::getTable('team_member'))
->ensureColumn(new rex_sql_column('twitter', 'varchar(64)', true))
->ensure();
rex_yform_manager_table_api::setTableField(rex::getTable('team_member'), [
'type_id' => 'value',
'type_name' => 'text',
'name' => 'twitter',
'label' => 'Twitter handle',
'prio' => 6,
]);
}
Tablesets let you ship a table definition as JSON (Table Manager → Export). Useful for:
Structure:
{
"rex_team_member": {
"table": {
"status": 1,
"table_name": "rex_team_member",
"name": "Team members",
"list_amount": 30,
"search": 1,
"hidden": 0,
"export": 1,
"import": 0
},
"fields": [
{
"type_id": "value",
"type_name": "text",
"db_type": "varchar(255)",
"name": "name",
"label": "Name",
"list_hidden": 0,
"search": 1
}
]
}
}
For production projects, prefer programmatic definition in install.php over imported tablesets — tablesets aren't versioned with the rest of your code.
prio – fields appear in random order. Use ascending integers per topic block (1-99 = values, 100-199 = validators).rex_team_member instead of rex::getTable('team_member') – breaks multi-instance / non-default-prefix setups.createdate / updatedate columns – YForm's standard datestamp fields silently break without them.value field – sort by prio so values come first, then validators.rex_yform_manager_table_api::setTable() without first creating the SQL table via rex_sql_table::ensure() – YForm metadata then references a nonexistent table.