Purpose

The hook for substituting the rendered content of a page. When Site::renderContent() is about to produce HTML from the resolved page, it first dispatches this event. A listener can fill the html slot with its own rendered output; Site uses that and skips the default Parsedown sanitizer pipeline.

The bundled scriptor-markdown-pages plugin uses it: during PageResolving it parses the markdown file into HTML; here it returns the cached HTML so the page does not get re-processed by Parsedown. Same shape applies to any plugin that owns its own rendering pipeline (a CommonMark variant with extensions, a Twig-rendered virtual page, etc.).

FQCN + file path

When to use

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

  • Replace the rendered HTML of a specific page (your virtual page, your custom-rendering page-type).
  • Skip the default Markdown rendering for pages whose content is already HTML (or some other format your plugin handles).

You do not use this event to mutate already-rendered HTML from other plugins; the event exposes the source Page, not the in-flight content. If you need post-processing of the default output, leave the slot alone (default pipeline runs) and do your post-processing somewhere closer to the template.

Surface

Public properties

Property Type Purpose
page readonly Page The page about to be rendered
html ?string (mutable) The slot listeners fill. Starts null. First non-null wins

No methods.

Constructor

public function __construct(public readonly Page $page)

Site::renderContent() constructs the event with the current page. You do not construct it yourself.

Lifecycle

Fires once per renderContent() call (usually once per request, unless a theme calls $site->render('content') more than once). After every listener has run, Site::renderContent() reads $event->html:

  • Non-null string: that becomes the rendered content. The default Parsedown pipeline is skipped entirely.
  • Null: the default $site->sanitizer->markdown($page->content) pipeline runs.

Listeners run in registration order. The "first writer wins" convention is enforced by the listener: subsequent listeners should self-check $event->html !== null and leave the slot alone unless they intentionally want to wrap the prior output.

Common patterns

Substituting HTML for a virtual page (markdown-pages-style)

$context->subscribe(
    \Scriptor\Boot\Events\Frontend\ContentRendering::class,
    function (\Scriptor\Boot\Events\Frontend\ContentRendering $event): void {
        if ($event->html !== null) {
            return;   // someone already rendered
        }
        $cached = $this->htmlCache->get($event->page->id());
        if ($cached !== null) {
            $event->html = $cached;
        }
    },
);

Detecting your own page-type and rendering with a custom pipeline

$context->subscribe(
    ContentRendering::class,
    function (ContentRendering $event): void {
        if ($event->html !== null) {
            return;
        }
        if ($event->page->pagetype !== 'rich-cms') {
            return;
        }
        $event->html = $this->richRenderer->render($event->page->content);
    },
);

Wrapping the default output (the rare case)

If you actually want to wrap whatever the default pipeline produces, you have to render the default yourself and use html to substitute the wrapped result. The event does not expose the in-flight default output:

$context->subscribe(
    ContentRendering::class,
    function (ContentRendering $event) use ($sanitizer): void {
        if ($event->html !== null) {
            return;   // do not stomp another plugin's substitution
        }
        $inner = $sanitizer->markdown($event->page->content);
        $event->html = '<article class="enhanced">' . $inner . '</article>';
    },
);

In practice, theme-level wrapping is cleaner than wrapping at this layer. Reach for this only when the wrapping has to apply across themes.

See also

  • PageResolving: where markdown-pages computes the HTML it later returns from this event
  • PageResolved: fires before this event; for hooks that should run regardless of who renders
  • Page: the source page the event carries
  • Site: the renderContent() method that dispatches this event
  • Sanitizer: the default markdown() method that runs when no listener fills the slot
  • Concept: Frontend Events: pipeline overview