In Using template UI elements to extend Craft CMS control panel article, I talked about enhancing control with UI elements. While useful, UI elements can be added only into field layouts. In this article, I will describe template hooks which are more versatile.
Template hooks #
Template hooks are Twig tags that can have content injected into them using PHP code. While you could potentially add hooks to your own templates, their main power lies in injecting content into layouts made by other people - into the control panel or interface of plugins. Here's an example hook used by Craft control panel template, placed in the sidebar of "edit entry" page. This specific hook is located in templates\entries_edit.html file:
{% hook 'cp.entries.edit.meta' %}
Plugins sometimes use template hooks to inject their functionality into the control panel. For example, seomatic SEO preview is injected into entry edit page that way.
You can find list of control panel hooks on Template Hooks page in Craft documentation. Craft Commerce also has many template hooks, listed in its documentation. Plugins sometimes have template hooks that are not documented - you can easily find them by searching for hook
in their source code.
Injecting content into template hooks #
Time to use hooks in practice. Place this code into your main module file init()
method:
Craft::$app->view->hook('cp.entries.edit.meta', function(array &$context) {
return '<p>Hello there.</p>';
});
This will make "Hello there." appear in entry edit page sidebar. Now, how about using Twig template to render content and append it to hook?
Craft::$app->getView()->hook('cp.entries.edit.meta', function(array &$context) {
$templatePath = 'some_template';
$context['someVariable'] = 'xyz';
$html = Craft::$app->view->renderTemplate(
$templatePath, $context, Craft::$app->view::TEMPLATE_MODE_SITE
);
return $html;
});
This will take some_template
Twig file from our templates
directory, render it and inject content into hook. Note that we passed $context
variable to our rendering function (and even added a new someVariable
to it) - now some_template
will have access to all variables available in template where hook tag was used. In our case, we will be able to use {{entry}}
variable.
If we want to place our template not in templates
directory, but in our module directory, we need to register new template root. Put this code in module init()
method:
\yii\base\Event::on(
\craft\web\View::class,
\craft\web\View::EVENT_REGISTER_SITE_TEMPLATE_ROOTS,
function(\craft\events\RegisterTemplateRootsEvent $event) {
$event->roots['customHandle'] = __DIR__ . '/custom_templates';
}
);
Now you will be able to include Twig files placed in custom_templates
directory that lives within your module - just use customHandle
prefix when specifying their path. With our example, you should use customHandle/some_template
path. Keep in mind that unlike modules, plugins have their template roots already defined - prefix is set to plugin handle.
Example use - Freeform related elements #
Here's an example use of template hooks functionality. Freeform plugin allows us to relate elements to form submissions. For example, when we have a form on some kind of product page, visitors can ask questions about this specific product - and form submission will be related to that product in the database.
The problem is, that Freeform does not display these related elements on submission page in control panel. It only allows for displaying related submissions on the entry page - so other way around. We can easily add this missing functionality to Freeform interface using template hooks.
Here's our Twig template:
{% set relatedElements = craft.entries.relatedTo(submission).all() %}
{% if relatedElements is not null %}
<div class="meta read-only">
<strong>Related elements:</strong>
<br>
{% for element in relatedElements %}
<a href="{{element.getCpEditUrl()}}">{{element.title}}</a>
<br>
{% endfor %}
</div>
{% endif %}
As you can see, we use submission
variable representing a single submission element and loop through related entries (we could also do the same for categories, or other elements). submission
variable is available in the context of the template that has hook we are injecting content into.
Time to inject content into hook. We will use freeform.submissions.edit.meta
that will inject content into the submission page sidebar. This hook actually is not documented - I found it by searching through freeform templates code.
Craft::$app->getView()->hook('freeform.submissions.edit.meta', function(array &$context) {
$cart_template = '_admin/freeform_related';
$html = Craft::$app->view->renderTemplate(
$cart_template, $context, Craft::$app->view::TEMPLATE_MODE_SITE
);
return $html;
});
Here's the result. Injected content is sitting in a gray box which is styled thanks to use of meta
and read-only
classes, which were already used by control panel styling.