← All documentation
Topics

- Working with data
- Designer components
- Field controls
- Search & filtering
- Calendars
- PDF & printing
- Email
- SMS
- Notifications
- Security
- Helper functions
- JavaScript API
- Performance

# 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.

Who is this for? Owners and administrators. This guide is hidden from regular users. There is also an interactive companion at `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 rule

The rules:

- One folder per plugin, containing `Plugin.php`.
- The class lives in the `PpPhp\Plugins` namespace.
- The class `implements` the matching interface: `ViewPlugin`, `EditPlugin`, `FilterPlugin` or `ValidationPlugin` (all in `PpPhp\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' => []]; }
    }

Security: the value comes from the database and may contain anything. Wrap every value in `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 `name` must 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 `$value` so 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

1. Create the folder + `Plugin.php` under the right category (`plugins/View`, `Edit`, `Filter` or `Validation`).
2. Open Plugin Manager in the console. Your plugin appears in its category; make sure it is enabled.
3. 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.
4. Build the project — the field now renders with your control on the published pages.

Core plugins are owner-managed. Your custom plugins live alongside them and are yours to edit. Use Plugin Manager → Developer Guide for the interactive version of this reference.

## 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.

Golden safety rule: field values come from users and the database. Never put a raw value straight into HTML — always `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.