Skip to content

Pseudo-tags

A pseudo-tag is a function called like a template tag that returns a ready piece of HTML:

fenom
{icon 'edit' class='icon-sm'}

Handy for repeated fragments — icons, buttons, badges — that do not deserve a chunk of their own.

No parentheses, and never inside an expression

This is a tag, not a function, and it takes tag syntax: parameters separated by spaces, no parentheses, no commas. Anything else is a compile error, Function icon not found — that is a blank page and a 500:

fenom
{icon 'edit' class='icon-sm'}     {* correct *}
{icon('edit')}                    {* 500 *}
{set $i = icon('edit')}           {* 500 *}
{if icon('edit')}                 {* 500 *}

If you need the value in a variable or a condition, write a modifier instead: it works as a filter, as a function and inside expressions.

📁 Where to declare them

core/App/Helpers/fenom/inline_tags.php

The file returns a name → function map. The first argument is the parameter array: unnamed parameters land under numeric keys, named ones under their own.

php
return [
    'icon' => function ($params) {
        // {icon 'edit' class='icon-sm'} → ['edit', 'class' => 'icon-sm']
        $name = array_shift($params);

        $attrs = [];
        foreach ($params as $key => $value) {
            $attrs[] = htmlspecialchars($key) . '="' . htmlspecialchars($value) . '"';
        }
        $attrStr = $attrs ? ' ' . implode(' ', $attrs) : '';

        $path = MODX_BASE_PATH . "assets/icons/{$name}.svg";
        if (!file_exists($path)) {
            return "<!-- icon '{$name}' not found -->";
        }

        $svg = file_get_contents($path);
        return preg_replace('/<svg\b(.*?)>/', '<svg$1' . $attrStr . '>', $svg, 1);
    },
];
fenom
{icon 'edit' class='icon-sm text-muted'}
{icon 'user' class='icon-lg' aria-hidden='true'}

As with modifiers, $this->modx is available — the file is included from inside View.

🔧 Built-in pseudo-tags

The component registers its own; you do not declare these:

TagReturns
{csrf}<input type="hidden" name="_token" …>
{csrf_token}the token value alone
{meta_csrf}<meta name="csrf-token" …> for fetch requests
{spam}a hidden honeypot field
{recaptcha 'login'}a reCAPTCHA v3 field with the action and public key
{route 'home'}the URL of a registered route
{lang 'auth.title'}a translation string
{pb_filters table='Products'}a ready-made table filter form
{heroicon $block.icon style='solid'}an inlined Heroicon
{pbList parent=$id tpl='item' limit=10}a resource lister with AJAX pagination

route and lang also exist as modifiers, where they do more (substitutions, an explicit language) and work inside expressions: {$item.uri|lang_url}, {route('order', ['id' => $id])}.

The callback contract

pbFenom keeps the historical addFunction() contract: the callback receives ($params, $tpl, $var), where $params is the parsed parameter array. That is exactly how inline_tags.php is wired up.

If you want the function's own signature to be the template API, register it with addFunctionSmart(). There is no dedicated hook file for that: View::init() puts the engine instance into modx()->fenom, so take it from there — from a plugin on OnLoadWebDocument, for instance, or from a snippet:

php
modx()->fenom->addFunctionSmart('excerpt', function (string $text, int $length = 100) {
    return mb_substr(strip_tags($text), 0, $length);
});
fenom
{excerpt text=$page.introtext length=200}

In this fork addFunctionSmart() accepts any callable — a closure, an array callable, an object with __invoke; upstream only ever handled strings. A missing required argument is reported at compile time ("Function excerpt requires the 'text' argument") instead of blowing up mid-render.

© PageBlocks 2019-present