Frontend hooks
UI render hooks and filter hooks for injecting content into the frontend layout and filtering rendered page output.
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>·</span><span>5 min read</span>';
});
Notes
- Each item in the meta row is conventionally preceded by
<span>·</span>as a separator. - Use
int $priority(third argument toHook::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, beforeWidgetArea::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
$titlestring. - Called for both pages and posts. Check
$content->typeif 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
$htmlstring. - Output is rendered with
{!! $html !!}(unescaped). Only return trusted HTML from your callbacks.