Contensio logo

Frontend hooks

UI render hooks and filter hooks for injecting content into the frontend layout and filtering rendered page output.

Since 1.5.0

These hooks operate on the Contensio frontend layout (layout.blade.php) and content views (post.blade.php, page.blade.php). They let plugins inject markup into standard positions and modify rendered output without touching theme files.

Note: These hooks only fire when a theme calls them explicitly. The default theme (contensio/default) calls all of them. If you build a custom theme, include the calls yourself — plugins depend on them.


contensio/frontend/head

Type: UI render hook
Since: 1.5.0
Source: layout.blade.php — inside <head>, before </head>

Inject HTML into the document <head>. Use this for meta tags, stylesheet links, font preloads, or any tag that must live in the head.

Arguments

None.

Theme call

{!! \Contensio\Support\Hook::render('contensio/frontend/head') !!}

Plugin example

use Contensio\Support\Hook;

Hook::add('contensio/frontend/head', function (): string {
    return '<link rel="stylesheet" href="https://cdn.example.com/plugin.css">';
});

contensio/frontend/body-start

Type: UI render hook
Since: 1.5.0
Source: layout.blade.php — immediately after the opening <body> tag

Inject markup immediately after <body>. The primary use-case is Google Tag Manager's <noscript> iframe and other tag manager fallbacks that must appear at the very top of the body.

Arguments

None.

Theme call

{!! \Contensio\Support\Hook::render('contensio/frontend/body-start') !!}

Plugin example

Hook::add('contensio/frontend/body-start', function (): string {
    $gtmId = 'GTM-XXXXXXX';
    return "<noscript><iframe src=\"https://www.googletagmanager.com/ns.html?id={$gtmId}\"
        height=\"0\" width=\"0\" style=\"display:none;visibility:hidden\"></iframe></noscript>";
});

contensio/frontend/body-end

Type: UI render hook
Since: 1.5.0
Source: layout.blade.php — before </body>, after @stack('scripts')

Inject markup at the very end of the document body. Use this for deferred JavaScript, chat widgets, analytics snippets, or any script that should load after page content.

Arguments

None.

Theme call

{!! \Contensio\Support\Hook::render('contensio/frontend/body-end') !!}

Plugin example

Hook::add('contensio/frontend/body-end', function (): string {
    return '<script src="https://cdn.example.com/chat-widget.js" defer></script>';
});

Notes

  • Runs after @stack('scripts') — the last thing before </body>.

contensio/frontend/post-meta

Type: UI render hook
Since: 1.5.0
Source: post.blade.php — inside the post meta row, after the publish date

Inject items into the post meta row (the line showing author, date, reading time, etc.). Use this for reading time estimates, word counts, print buttons, or any inline badge or action that belongs in the meta row.

Arguments

# Name Type Description
1 $content Content The post being viewed.
2 $translation ContentTranslation The active language translation.

Theme call

{!! \Contensio\Support\Hook::render('contensio/frontend/post-meta', $content, $translation) !!}

Plugin example

use Contensio\Models\Content;
use Contensio\Models\ContentTranslation;

Hook::add('contensio/frontend/post-meta', function (Content $content, ContentTranslation $translation): string {
    return '<span>&middot;</span><span>5 min read</span>';
});

Notes

  • Each item in the meta row is conventionally preceded by <span>&middot;</span> as a separator.
  • Use int $priority (third argument to Hook::add()) to control ordering between multiple plugins.

contensio/frontend/post-before-content

Type: UI render hook
Since: 1.5.0
Source: post.blade.php — between the excerpt and the block content area

Inject markup between the post excerpt and the main block content. Use this for table of contents, paywalls, subscription prompts, or content warnings.

Arguments

# Name Type Description
1 $content Content The post being viewed.
2 $translation ContentTranslation The active language translation.

Theme call

{!! \Contensio\Support\Hook::render('contensio/frontend/post-before-content', $content, $translation) !!}

Plugin example

Hook::add('contensio/frontend/post-before-content', function (Content $content, ContentTranslation $translation): string {
    return view('my-plugin::partials.toc', compact('content'))->render();
});

contensio/frontend/post-after-content

Type: UI render hook
Since: 1.5.0
Source: post.blade.php — after the block content area, inside <article>

Inject markup directly after the post body blocks, still inside the <article> element. Use this for author bios, social share buttons, related posts, or newsletter sign-up calls to action.

Arguments

# Name Type Description
1 $content Content The post being viewed.
2 $translation ContentTranslation The active language translation.

Theme call

{!! \Contensio\Support\Hook::render('contensio/frontend/post-after-content', $content, $translation) !!}

Plugin example

Hook::add('contensio/frontend/post-after-content', function (Content $content, ContentTranslation $translation): string {
    return view('my-plugin::partials.author-box', compact('content'))->render();
}, 5);

Notes

  • Priority 5 renders first (e.g. author box), priority 15 renders last (e.g. related posts).
  • Runs inside the <article> tag, before WidgetArea::render('after-post').

contensio/frontend/page-title

Type: Filter
Since: 1.5.0
Source: page.blade.php, post.blade.php@section('title', ...)

Filter the string used as the HTML <title> tag value. The default value is {meta_title or title} — {site name}.

Arguments

# Name Type Description
1 $title string The current <title> string.
2 $content Content The content entry being viewed.

Theme call

@section('title', apply_filters('contensio/frontend/page-title', $translation->title . ' — ' . $site['name'], $content))

Plugin example

use Contensio\Models\Content;

add_filter('contensio/frontend/page-title', function (string $title, Content $content): string {
    if (now()->between('2026-11-25', '2026-11-30')) {
        return $title . ' | Black Friday Sale';
    }
    return $title;
}, 10);

Notes

  • You must return the $title string.
  • Called for both pages and posts. Check $content->type if you need type-specific logic.

contensio/content/body

Type: Filter
Since: 1.5.0
Source: page.blade.php, post.blade.php — wraps the rendered block HTML

Filter the full rendered HTML of a content entry before it is output to the browser. The value is the complete HTML string produced by rendering all content blocks.

Arguments

# Name Type Description
1 $html string The rendered block HTML.
2 $content Content The content entry being viewed.

Theme call

@php ob_start(); @endphp
<div class="contensio-post-body">
    @foreach($content->blocks ?? [] as $block)
        @include('theme::partials.block', ['block' => $block, 'langId' => $lang?->id])
    @endforeach
</div>
@php $body = ob_get_clean(); @endphp
{!! apply_filters('contensio/content/body', $body, $content) !!}

Plugin example — inject an ad after the third paragraph

use Contensio\Models\Content;

add_filter('contensio/content/body', function (string $html, Content $content): string {
    if ($content->type !== 'post') {
        return $html;
    }

    $ad = '<div class="ad-unit my-8"><!-- ad slot --></div>';

    $pos = 0;
    for ($i = 0; $i < 3; $i++) {
        $found = strpos($html, '</p>', $pos);
        if ($found === false) break;
        $pos = $found + 4;
    }

    if ($pos > 0) {
        $html = substr($html, 0, $pos) . $ad . substr($html, $pos);
    }

    return $html;
});

Plugin example — add target="_blank" to external links

add_filter('contensio/content/body', function (string $html): string {
    $host = parse_url(config('app.url'), PHP_URL_HOST) ?? '';

    return preg_replace_callback(
        '/<a\s([^>]*)href=["\']?(https?:\/\/[^"\'>\s]+)["\']?([^>]*)>/i',
        function (array $m) use ($host): string {
            $linkHost = parse_url($m[2], PHP_URL_HOST) ?? '';
            if ($linkHost === $host || str_ends_with($linkHost, '.' . $host)) {
                return $m[0];
            }
            $attrs = preg_replace('/\s*(target|rel)=["\'][^"\']*["\']/i', '', $m[1] . $m[3]);
            return '<a ' . trim($attrs) . ' href="' . $m[2] . '" target="_blank" rel="noopener noreferrer">';
        },
        $html
    ) ?? $html;
});

Notes

  • You must return the $html string.
  • Output is rendered with {!! $html !!} (unescaped). Only return trusted HTML from your callbacks.