Building Field Plugins
Field plugins are the little controls that decide how a single table field looks and behaves — a date picker for editing, a coloured badge for viewing, a range slider for filtering. This guide shows you how to build your own, from a blank file to a working control in the Designer, for beginners.
Plugin Manager → Developer Guide.
Contents
- What is a field plugin? (four kinds)
- Where plugins live & the file shape
- Anatomy of a plugin (the five methods)
- The settings panel (
settingsSchema) - Example 1 — a View plugin (coloured badge)
- Example 2 — an Edit plugin (colour input)
- Example 3 — a Filter plugin
- Example 4 — a Validation rule
- Extra JS/CSS files (
assets) - Bonus: option lists from a table (
LookupOptions) - Installing & enabling your plugin
- Checklist & safety notes
What is a field plugin? (four kinds)
When you design a table’s pages, each field can be assigned a control. There are four categories, each with one job:
| Category | Folder | Job | Example |
|---|---|---|---|
| View | plugins/View | Show a value on list/view pages | Badge, Currency, Image, Hyperlink |
| Edit | plugins/Edit | An input on add/edit forms | Date Picker, Dropdown, Checkbox, Password |
| Filter | plugins/Filter | A search control on list pages | Text Filter, Date Range, Number Range |
| Validation | plugins/Validation | A rule that checks input before saving | Email, Min length, Numeric range |
Where plugins live & the file shape
Every plugin is a single PHP class in its own folder:
plugins/
View/
Badge/Plugin.php ← a View plugin
Currency/Plugin.php
Edit/
DatePicker/Plugin.php ← an Edit plugin
Dropdown/Plugin.php
Filter/
TextFilter/Plugin.php ← a Filter plugin
Validation/
Email/Plugin.php ← a Validation ruleThe rules:
- One folder per plugin, containing
Plugin.php. - The class lives in the
PpPhp\Pluginsnamespace. - The class
implementsthe matching interface:ViewPlugin,EditPlugin,FilterPluginorValidationPlugin(all inPpPhp\Core).
Anatomy of a plugin (the five methods)
Every View / Edit / Filter plugin implements the same five static methods:
| Method | Returns | What it does |
|---|---|---|
id() | string | A unique id, e.g. 'Badge'. |
label() | string | The friendly name shown in the picker. |
render(…) | string (HTML) | Produces the control’s HTML. The signature differs per category (see below). |
settingsSchema() | array | The options shown in the properties panel. |
assets() | array | Extra JS/CSS files: ['js'=>[], 'css'=>[]]. |
The one difference — the render() signature:
// View — you get the value + settings, return display HTML
public static function render(mixed $value, array $settings): string
// Edit — you also get the field NAME (for the <input name="…">)
public static function render(string $fieldName, mixed $value, array $settings): string
// Filter — the field name + settings (there is no single value to show)
public static function render(string $fieldName, array $settings): string
The settings panel (settingsSchema)
settingsSchema() returns a list of option definitions. Each becomes a control in the
field’s properties panel, and the values come back to you in the $settings array.
It uses the same field shapes as components:
public static function settingsSchema(): array
{
return [
['key' => 'color', 'label' => 'Colour', 'type' => 'select',
'options' => ['primary', 'success', 'warning', 'danger'], 'default' => 'primary'],
['key' => 'icon', 'label' => 'Icon', 'type' => 'text', 'default' => ''],
['key' => 'rounded','label' => 'Rounded','type' => 'bool', 'default' => 'no'],
];
}Supported type values: text, number, select
(with options), bool, color, textarea. Read the
chosen values in render() from $settings['key'] — always with a
fallback: $settings['color'] ?? 'primary'.
Example 1 — a View plugin (a coloured status badge)
Create plugins/View/StatusDot/Plugin.php. It shows a value with a small coloured dot
— useful for status columns.
<?php
namespace PpPhp\Plugins;
use PpPhp\Core\ViewPlugin;
class StatusDot implements ViewPlugin
{
public static function id(): string { return 'StatusDot'; }
public static function label(): string { return 'Status dot'; }
public static function render(mixed $value, array $settings): string
{
$val = (string) ($value ?? '');
if ($val === '') return '<span class="pp-muted">—</span>';
// A colour per value, e.g. "active:success, pending:warning"
$map = [];
foreach (explode(',', (string) ($settings['colorMap'] ?? '')) as $pair) {
if (!str_contains($pair, ':')) continue;
[$k, $col] = array_map('trim', explode(':', $pair, 2));
$map[strtolower($k)] = $col;
}
$color = $map[strtolower($val)] ?? (string) ($settings['color'] ?? 'secondary');
// Always escape the value before printing it.
return '<span class="pp-dot pp-bc-' . htmlspecialchars($color) . '"></span> '
. htmlspecialchars($val);
}
public static function settingsSchema(): array
{
return [
['key' => 'color', 'label' => 'Default colour', 'type' => 'select',
'options' => ['secondary', 'primary', 'success', 'warning', 'danger'], 'default' => 'secondary'],
['key' => 'colorMap', 'label' => 'Colour map (value:colour, …)', 'type' => 'text', 'default' => ''],
];
}
public static function assets(): array { return ['js' => [], 'css' => []]; }
}htmlspecialchars() before you put it in HTML.Example 2 — an Edit plugin (a native colour input)
Create plugins/Edit/ColorInput/Plugin.php. Edit plugins must render a form input whose
name is the field name, so the value is submitted.
<?php
namespace PpPhp\Plugins;
use PpPhp\Core\EditPlugin;
class ColorInput implements EditPlugin
{
public static function id(): string { return 'ColorInput'; }
public static function label(): string { return 'Colour picker'; }
public static function render(string $fieldName, mixed $value, array $settings): string
{
$val = (string) ($value ?? ($settings['default'] ?? '#4f46e5'));
$name = htmlspecialchars($fieldName);
$show = ($settings['showHex'] ?? 'yes') !== 'no';
$html = '<span class="pp-colorpick">'
. '<input type="color" name="' . $name . '" id="field_' . $name . '"'
. ' value="' . htmlspecialchars($val) . '">';
if ($show) {
// A read-only text box that mirrors the picked colour (progressive JS).
$html .= '<output>' . htmlspecialchars($val) . '</output>'
. '<script>(function(){var i=document.getElementById("field_' . $name . '");'
. 'if(!i||i.__b)return;i.__b=1;i.addEventListener("input",function(){'
. 'i.nextElementSibling.textContent=i.value;});})();</script>';
}
return $html . '</span>';
}
public static function settingsSchema(): array
{
return [
['key' => 'default', 'label' => 'Default colour', 'type' => 'color', 'default' => '#4f46e5'],
['key' => 'showHex', 'label' => 'Show hex value', 'type' => 'bool', 'default' => 'yes'],
];
}
public static function assets(): array { return ['js' => [], 'css' => []]; }
}Key points for Edit plugins:
- The input’s
namemust be exactly$fieldName, or the value won’t save. - Use
id="field_<name>"for the main control — the rest of the form wiring expects that id. - Pre-fill the current
$valueso editing an existing record shows what is stored.
Example 3 — a Filter plugin
Create plugins/Filter/StartsWith/Plugin.php. Filter inputs must be named
filter_<fieldName> so the list page picks them up.
<?php
namespace PpPhp\Plugins;
use PpPhp\Core\FilterPlugin;
class StartsWith implements FilterPlugin
{
public static function id(): string { return 'StartsWith'; }
public static function label(): string { return 'Starts with'; }
public static function render(string $fieldName, array $settings): string
{
$name = htmlspecialchars($fieldName);
$ph = htmlspecialchars((string) ($settings['placeholder'] ?? ('Starts with…')));
return '<input type="text" name="filter_' . $name . '" class="form-control form-control-sm"'
. ' placeholder="' . $ph . '">';
}
public static function settingsSchema(): array
{
return [
['key' => 'placeholder', 'label' => 'Placeholder', 'type' => 'text', 'default' => 'Starts with…'],
];
}
public static function assets(): array { return ['js' => [], 'css' => []]; }
}
Example 4 — a Validation rule
Validation plugins are slightly different: they check a value before it is saved. Create
plugins/Validation/NoSpaces/Plugin.php.
<?php
namespace PpPhp\Plugins;
use PpPhp\Core\ValidationPlugin;
class NoSpaces implements ValidationPlugin
{
public static function id(): string { return 'no_spaces'; }
public static function label(): string { return 'No spaces'; }
public static function group(): string { return 'Format'; }
public static function paramsSchema(): array { return []; } // no options
/** Field types this rule is offered for (hint only). */
public static function appliesTo(): array { return ['text', 'email']; }
/** {label} is replaced with the field's label. */
public static function defaultMessage(): string { return '{label} must not contain spaces.'; }
/** Return TRUE when the value is VALID. Empty values are skipped automatically. */
public static function validate(mixed $value, array $params): bool
{
return !str_contains((string) $value, ' ');
}
}Validation rules appear in the field’s Validation section of the Designer. The same rule runs both in the browser and on the server, so bad data can never be saved.
Extra JS/CSS files (assets)
If your control needs a stylesheet or a script file, put them in the plugin folder and list them
in assets(). They are copied into the built app and loaded only on pages that use the
control.
public static function assets(): array
{
return [
'css' => ['style.css'], // plugins/<Cat>/<Name>/style.css
'js' => ['behaviour.js'], // plugins/<Cat>/<Name>/behaviour.js
];
}For tiny scripts, a small inline <script> inside render() (as in the
colour-input example) is fine and keeps everything in one file.
Bonus — option lists from a table (LookupOptions)
Dropdown-style controls often need their options from a database table. The
LookupOptions helper does this for you: add its schema to yours, and resolve the options
in render().
use PpPhp\Core\Designer\FieldControl\LookupOptions;
public static function render(string $fieldName, mixed $value, array $settings): string
{
// Returns [ value => label, … ] from a manual list OR a table lookup,
// based on the settings the user filled in.
$options = LookupOptions::resolve($settings);
// …build your <select>/buttons from $options…
}
public static function settingsSchema(): array
{
return array_merge([
// …your own options…
], LookupOptions::schema()); // adds "Options source", table, value/label columns, etc.
}
Installing & enabling your plugin
- Create the folder +
Plugin.phpunder the right category (plugins/View,Edit,FilterorValidation). - Open Plugin Manager in the console. Your plugin appears in its category; make sure it is enabled.
- In the Designer, select a field, open its properties, and choose your control
under View control / Edit control / Filter. Its settings (from
settingsSchema) appear right there. - Build the project — the field now renders with your control on the published pages.
Checklist & safety notes
| Done? | Step |
|---|---|
| ☐ | Folder plugins/<Category>/<Name>/Plugin.php created. |
| ☐ | Class in namespace PpPhp\Plugins, implements the right interface. |
| ☐ | All five methods present (id, label, render, settingsSchema, assets). |
| ☐ | render() uses the correct signature for its category. |
| ☐ | Edit control’s input uses name="<fieldName>"; Filter uses name="filter_<fieldName>". |
| ☐ | Every value/setting printed to HTML is wrapped in htmlspecialchars(). |
| ☐ | Enabled in Plugin Manager and selected on a field in the Designer. |
htmlspecialchars() it first. This
prevents broken pages and cross-site-scripting (XSS) attacks.
See also: the Designer Components guide for building whole drag-and-drop blocks.