Purpose

The editor-side hook for adding fields to the page edit form without forking PagesModule. When the Pages module renders the create or edit form, it dispatches this event once at the top. A listener appends its own input markup into a named slot, and may mark core fields hidden when they do not apply to a page-type. PageSaving is the companion that persists what these inputs post.

Added in Scriptor 2.1.0 (hide() / isHidden() shipped in the same release, after the base slot API).

FQCN + file path

When to use

Subscribe inside Plugin::register() when you want to:

  • Add inputs to the page form (SEO meta, a price, a flag) that save into the page's data bag.
  • Remove core fields that a page-type does not need (a product child has no use for menu_title).

Pair it with PageSaving: this event renders the input, that one stores the posted value. Reach for an editor module instead when the admin surface is a separate screen rather than fields on the existing page form.

Surface

Public properties

Property Type Purpose
page readonly ?Page The page being edited; null on the new-page flow
categoryId readonly int The iManager category id the form edits

Public methods

public function appendHtml(string $html, string $slot = self::SLOT_END): void

Append markup into a named slot. Buffers concatenate when several listeners target the same slot. The buffer is printed verbatim, so the listener owns its HTML escaping (the event does not re-encode).

public function hide(string $field): void
public function isHidden(string $field): bool

hide() marks a core field skipped; PagesModule consults isHidden() before rendering each built-in field. Field names match the POST input names: name, menu_title, slug, content, images, parent, template, position, published. A hidden field posts no value, so its stored data is carried forward untouched on save.

public function htmlFor(string $slot): string

Returns a slot's accumulated buffer. Called by PagesModule, not by listeners.

Slot constants

Ten constants, one slot after each core field plus an end slot: SLOT_AFTER_NAME, SLOT_AFTER_MENU_TITLE, SLOT_AFTER_SLUG, SLOT_AFTER_CONTENT, SLOT_AFTER_IMAGES, SLOT_AFTER_PARENT, SLOT_AFTER_TEMPLATE, SLOT_AFTER_POSITION, SLOT_AFTER_PUBLISHED, SLOT_END. appendHtml() defaults to SLOT_END (just before the hidden action/csrf inputs) when no slot is given.

Constructor

public function __construct(public readonly ?Page $page, public readonly int $categoryId)

PagesModule constructs it. You do not construct it yourself.

Lifecycle

Fires once per edit-form render, inside PagesModule::renderForm(). After dispatch, PagesModule walks the core fields in order: for each one it checks isHidden() and skips the field if a listener hid it, then prints htmlFor(SLOT_AFTER_<field>). SLOT_END prints last, before the form's hidden inputs. Listeners run in registration order; slot buffers from multiple plugins concatenate in that order.

The event carries no state beyond the request. The page it references is the one currently being edited (or null when the operator is creating a new page, so always read $event->page?->… defensively).

Common patterns

Add a field under the Content textarea

$context->subscribe(
    \Scriptor\Boot\Events\Editor\PageFormRendering::class,
    function (\Scriptor\Boot\Events\Editor\PageFormRendering $event): void {
        $current = (string) ($event->page?->meta_title ?? '');
        $event->appendHtml(
            '<div class="form-control"><label for="meta-title">Meta title</label>'
            . '<input id="meta-title" name="meta_title" type="text" value="'
            . htmlspecialchars($current, ENT_QUOTES) . '"></div>',
            \Scriptor\Boot\Events\Editor\PageFormRendering::SLOT_AFTER_CONTENT,
        );
    },
);

Hide a core field for a page-type

$context->subscribe(
    PageFormRendering::class,
    function (PageFormRendering $event): void {
        if ($event->page?->template !== 'produkt') {
            return;
        }
        $event->hide('menu_title');   // products are not nav items
        $event->appendHtml($priceInput, PageFormRendering::SLOT_AFTER_SLUG);
    },
);

Core fields are read from $_POST after this event fires, so PageSaving::mergeData() cannot override a built-in value. To replace a core field with your own widget, hide() it and re-inject an input under the same name.

See also

  • PageSaving: the persist-side companion; the two are almost always subscribed together
  • Page: the DTO $event->page carries; __get resolves data-bag keys such as $page->meta_title
  • Plugin: where you subscribe, in register()
  • Module: the alternative when the admin surface is a separate screen rather than page-form fields
  • Cookbook: Add fields to the page edit form: the worked recipe, including the JSON-blob storage round trip
  • Concept: Editor extensions: how editor events, modules, and the container fit together