Insert Tags

Insert tags are a Contao specific way to replace specific tokens in your templates and database fields with content. They follow the format {{TAG_NAME}}. In most cases they contain parameters after the tag name, ie: {{TAG_NAME::PARAMETERS}}.

A list of readily available insert tags can be found in the user manual.

Register custom insert tags

since 5.2 Custom insert tags can be registered using the PHP attributes AsInsertTag and AsBlockInsertTag or their corresponding service tags contao.insert_tag and contao.block_insert_tag. The following options are supported for these service tags:

OptionDescription
nameThe name of the insert tag. Has to be in lowercase.
resolveNestedTagstrue will resolve all nested tags before passing it to the method. false will keep nested tags unreplaced.
priorityFor multiple insert tags with the same name only the one with the highest priority will be executed.
methodWill default to __invoke or the name of the method the attribute is attached to. Otherwise a method name has to be defined.
asFragmentIf enabled the insert tag will be rendered as a fragment via an <esi> tag. Only for regular (not block) insert tags.
endTagThe name of the end tag. Has to be in lowercase. Only for block insert tags.

The following example will provide an insert tag which transforms a string with the str_rot13 function provided by PHP.

// src/InsertTag/Rot13InsertTag.php
namespace App\InsertTag;

use Contao\CoreBundle\DependencyInjection\Attribute\AsInsertTag;
use Contao\CoreBundle\InsertTag\Exception\InvalidInsertTagException;
use Contao\CoreBundle\InsertTag\InsertTagResult;
use Contao\CoreBundle\InsertTag\OutputType;
use Contao\CoreBundle\InsertTag\ResolvedInsertTag;
use Contao\CoreBundle\InsertTag\Resolver\InsertTagResolverNestedResolvedInterface;

#[AsInsertTag('rot13')]
class Rot13InsertTag implements InsertTagResolverNestedResolvedInterface
{
    public function __invoke(ResolvedInsertTag $insertTag): InsertTagResult
    {
        if (null === $insertTag->getParameters()->get(0)) {
            throw new InvalidInsertTagException('Missing parameters for insert tag.');
        }
        
        $parameter = $insertTag->getParameters()->get(0);
        
        $rotated = str_rot13($parameter);
        
        return new InsertTagResult($rotated, OutputType::text);
    }
}

Now the insert tag {{rot13::Contao}} will be replaced with Pbagnb.

Block Insert Tags

This allows you to implement insert tags that wrap around some content. An example for a block insert tag can be found in the Contao core bundle: the IfLanguageInsertTag. It allows you to conditionally output some content, depending on the page’s language, e.g.:

{{ifnlng::de}}Some English text.{{ifnlng}}
{{iflng::de}}Some German text.{{iflng}}

When implementing a block insert tag your function receives the wrapped content as a ParsedSequence object in addition to your InsertTag. The ParsedSequence object consists of strings and other parsed (but not yet resolved) insert tags. Your function is then expected to return a ParsedSequence again - which could be empty in case your block insert tag should not output anything (e.g. in the iflng example if the language does not match).

The following example implements a {{ifmembergroup::*}}…{{endifmembergroup}} block insert tag with which you can conditionally output some content depending on the member group of the logged in member:

// src/InsertTag/IfMemberGroupInsertTag.php
namespace App\InsertTag;

use Contao\CoreBundle\DependencyInjection\Attribute\AsBlockInsertTag;
use Contao\CoreBundle\InsertTag\Exception\InvalidInsertTagException;
use Contao\CoreBundle\InsertTag\ParsedSequence;
use Contao\CoreBundle\InsertTag\ResolvedInsertTag;
use Contao\CoreBundle\InsertTag\Resolver\BlockInsertTagResolverNestedResolvedInterface;
use Contao\CoreBundle\Security\ContaoCorePermissions;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;

#[AsBlockInsertTag('ifmembergroup', endTag: 'endifmembergroup')]
class IfMemberGroupInsertTag implements BlockInsertTagResolverNestedResolvedInterface
{
    public function __construct(
        private readonly AuthorizationCheckerInterface $auth
    ) {
    }

    public function __invoke(ResolvedInsertTag $insertTag, ParsedSequence $wrappedContent): ParsedSequence
    {
        if (!$groups = $insertTag->getParameters()->all()) {
            throw new InvalidInsertTagException('Missing parameters for insert tag.');
        }

        if ($this->auth->isGranted(ContaoCorePermissions::MEMBER_IN_GROUPS, $groups)) {
            return $wrappedContent;
        }

        return new ParsedSequence([]);
    }
}

InsertTag and InsertTagParameters objects

An insert tag resolver gets an InsertTag object passed, either as ParsedInsertTag or as ResolvedInsertTag depending on the resolveNestedTags attribute setting. A parsed insert tag can still include other nested insert tags in its parameters while in a resolved insert tag all parameters are directly available. For an insert tag like {{tag::foo{{nested}}bar}} these objects would look like:

/** @var ResolvedInsertTag $tag */
$tag->getParameters()->get(0); // "foonestedbar"

/** @var ParsedInsertTag $tag */
$tag->getParameters()->get(0); // object(ParsedSequence)
$tag->getParameters()->get(0)->get(0); // "foo"
$tag->getParameters()->get(0)->get(1); // object(ParsedInsertTag)
$tag->getParameters()->get(0)->get(2); // "bar"

The parameters objects can be used to access named, scalar or array parameters if desired:

// {{tag::key=value}}
$tag->getParameters()->get('key'); // "value"
$tag->getParameters()->get(0); // "key=value"

// {{tag::key=value1::key=value2}}
$tag->getParameters()->all('key'); // ["value1", "value2"]

// {{tag::int=1::float=1.2::string=value}}
$tag->getParameters()->getScalar('int'); // 1
$tag->getParameters()->getScalar('float'); // 1.2
$tag->getParameters()->getScalar('string'); // "value"

Register custom insert tag flags

since 5.2 Custom insert tag flags can be registered using the PHP attribute AsInsertTagFlag or the corresponding service tag contao.insert_tag_flag. The following options are supported for this service tag:

OptionDescription
nameThe name of the insert tag flag. Has to be in lowercase.
priorityFor multiple insert tag flags with the same name only the one with the highest priority will be executed.
methodWill default to __invoke or the name of the method the attribute is attached to. Otherwise a method name has to be defined.

The following example will provide an insert tag flag which transforms the output of the tag or the previous flag with the str_rot13 function provided by PHP.

// src/InsertTags/Rot13InsertTagFlag.php
namespace App\InsertTags;

use Contao\CoreBundle\DependencyInjection\Attribute\AsInsertTagFlag;
use Contao\CoreBundle\InsertTag\InsertTagResult;
use Contao\CoreBundle\InsertTag\OutputType;
use Contao\CoreBundle\InsertTag\ResolvedInsertTag;
use Contao\CoreBundle\InsertTag\Flag\InsertTagFlagInterface;

#[AsInsertTagFlag('rot13')]
class Rot13InsertTagFlag implements InsertTagFlagInterface
{
    public function __invoke(InsertTagFlag $flag, InsertTagResult $result): InsertTagResult
    {
        return $result
            ->withValue(str_rot13($result->getValue()))
            ->withOutputType(OutputType::text) // Switch to text here as rot13 results in unsafe HTML.
        ;
    }
}

Now the insert tag {{label::MSC:reset|rot13}} will be replaced with Erfrg.

Formal syntax (EBNF)

The formal syntax of insert tags is defined as follows:

   InsertTag ::= "{{" Name Parameter * Flag * "}}"
        Name ::= [a-z#x80-#xFF] [a-z0-9_#x80-#xFF] *
   Parameter ::= "::" ( KeyValuePair | Value )
        Flag ::= "|" [^{}|] *
KeyValuePair ::= Key "=" Value
         Key ::= [^{}|=] *
       Value ::= ( [^{}|] | InsertTag ) *

Create a custom Insert Tag hook

This section applies to Contao versions prior to 5.2. For later versions use the attributes mentioned above instead.

Custom insert tags can be replaced by creating a service tagged with the contao.hook tag and registering a specific Hook. This service will be called whenever the replacement takes place for each occurring insert tag. This means, you will need to filter out the specific tag you want to process yourself.

The following example will provide an insert tag which transforms a string with the str_rot13 function provided by PHP. It registers the replaceInsertTags hook using PHP attributes:

// src/EventListener/Rot13InsertTagListener.php
namespace App\EventListener;

use Contao\CoreBundle\DependencyInjection\Attribute\AsHook;

#[AsHook('replaceInsertTags')]
class Rot13InsertTagListener
{
    public const TAG = 'rot13';
    
    public function __invoke(string $tag)
    {
        $chunks = explode('::', $tag);

        if (self::TAG !== $chunks[0]) {
            return false;
        }
        
        return str_rot13($chunks[1]);
    }
}

Creating this file while using the Hook service annotation within is all you have to do in Contao 4.9 and upwards. See the framework article about Hooks on how to implement this Hook in previous Contao versions.

The Hook provides many additional parameters. See the reference article for this Hook for more information about the possible parameters.

Cache behaviour of legacy insert tags

This section applies to Contao versions prior to 5.2. For later versions take a look at the setting asFragment from above.

Generally, replaced insert tags will be cached and stored in the public cache. However, there are some exemptions worth noting.

The following tags will not be stored in the public cache, since they do not contain data suitable for caching.

  • date
  • ua
  • post
  • back
  • referer
  • request_token

Furthermore, if a tag starts with cache_ or has the flag uncached it will be converted to a private ESI response, and is therefore not cached publicly.

If the custom insert tag mentioned above should be exempted from the public cache add the uncached flag whenever used.

<div>{{rot13::Payload|uncached}}</div>

Using the uncached flag is deprecated and doesn’t work in Contao 5.0 anymore. Use the {{fragment::*}} insert tag instead.