This extension documentation is published under the
CC BY-NC-SA 4.0
(Creative Commons) license.
An extension for TYPO3 CMS that provides an entire rendering environment for
Handlebars templates. It is seamlessly integrated into TYPO3 and offers
extensive configuration options to get all the power out of your templates.
To meet everyone's needs, it is easily extensible using TYPO3 on-board tools.
Introduction
A quick overview about the main features provided by this extension.
Installation
Instructions on how to install this extension, and which TYPO3 and PHP versions
are currently supported.
Configuration
Learn how to configure the extension in various ways. This includes extension
configuration, site configuration and TypoScript configuration.
Usage
This section describes how to use this extension in various ways, and which
additional components exist.
Developer corner
A quick overview about all relevant classes provided by this extension.
Migration
Required migration steps when upgrading the extension to a new major version.
Introduction
What does it do?
The extension provides a full rendering environment for Handlebars templates
within TYPO3 CMS. All core features of
Handlebars.js are supported by the usage of the third-party library
PHP Handlebars.
Its main use is to seamlessly integrate Handlebars templates into TYPO3 without
the need to modify these templates again for output in TYPO3.
Features
Full rendering environment for Handlebars templates
Native support for custom Handlebars helpers
Easy to extend and customize
Built on dependency injection for better performance and maintainability
Integration with TYPO3's cache framework for compiled templates
Compatible with TYPO3 13.4 LTS and 14.3 LTS
Support
There are several ways to get support for this extension:
The extension provides two site sets that can be included in the site
configuration or any other site set.
cpsit/handlebars — Handlebars base
Wires
plugin.tx_handlebars.view.templateRootPaths and
plugin.tx_handlebars.view.partialRootPaths from the site settings
handlebars.view.templateRootPath and
handlebars.view.partialRootPath.
Include this set for every site that renders Handlebars templates.
cpsit/handlebars-content-element — Handlebars content elements
Sets
lib.contentElement = HANDLEBARSTEMPLATE, replacing the
default Fluid base object used by EXT:fluid_styled_content.
Include this set when all content elements should use Handlebars rendering by default.
Template and partial root paths are collected from various sources, each
with a distinct priority. Higher-priority sources win over lower-priority ones.
Within a single source, higher numeric keys override lower ones.
Template and partial root paths can be set directly inside a
HANDLEBARSTEMPLATE content object. These paths apply only to
that specific rendering, including any nested partial lookups triggered by it.
The
cpsit/handlebars site set also populates these paths
from the site settings
{$handlebars.view.templateRootPath} and
{$handlebars.view.partialRootPath}.
Note
When multiple extensions declare paths under the same numeric key, the last
one loaded wins. Use distinct keys (e.g., 10, 20, 30) to ensure all paths
are registered.
Service container (priority 0)
The lowest-priority source is the service container. Paths registered here
apply instance-wide, regardless of the current page or content object, and
serve as the global fallback.
The
HandlebarsExtension DI extension merges all paths declared this
way into the container parameters
%handlebars.templateRootPaths% and
%handlebars.partialRootPaths%.
Variables
Template variables are available at two scopes: globally for every rendering,
and locally for a single content object rendering.
When the same key is defined in both sources, the TypoScript value takes
precedence.
Per-rendering variables
Variables scoped to a single rendering are declared in the
variables property of a
HANDLEBARSTEMPLATE
content object. Each entry is processed as a standard content object
against the current record's data:
tt_content.header = HANDLEBARSTEMPLATE
tt_content.header {
templateName = Header
variables {
header = TEXT
header.field = header
subheader = TEXT
subheader.field = subheader
link = TEXT
link.typolink.parameter.field = header_link
}
}
Copied!
Entries with no sub-configuration are treated as simple variables and
passed to the template as-is, without invoking
ContentObjectRenderer:
variables {
# Content object — field value is rendered via cObjGetSingle
header = TEXT
header.field = header
# Simple variables — values are passed through directly
cssClass = my-element
theme = dark
}
Copied!
Two variables are always injected automatically and cannot be overridden
(this reflects the same behavior as in
FLUIDTEMPLATE):
data
The full data array of the current content element record.
current
The value of the current field (
$cObj->currentValKey).
Warning
Declaring
data or
current in
variables is not allowed and raises an exception.
The extension registers a cache named handlebars that stores compiled
Handlebars templates. The cache is registered automatically on extension
activation; no manual setup is required.
The default backend is the TYPO3 database cache. To use a different backend,
add an override to your extension's ext_localconf.php file:
ext_localconf.php
if (!isset($GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations']['handlebars']['backend'])) {
$GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations']['handlebars']['backend']
= \TYPO3\CMS\Core\Cache\Backend\SimpleFileBackend::class;
}
Copied!
See also
Caching in TYPO3 for all
available backends and their configuration options.
Debugging
When TYPO3 frontend debug mode is active, the Handlebars renderer emits
additional debug output for individual template tags. This makes it easier
to localize rendering errors during development.
Note
Debug output is only produced by the built-in
HandlebarsRenderer.
Custom renderer implementations do not inherit this behaviour unless they
implement it explicitly.
This page walks through a minimal working example: rendering the header
CType with a Handlebars template.
Include site sets
The extension ships two site sets. Include them in your site's
configuration via the site module or config/sites/<site>/sets.yaml.
cpsit/handlebars (required)
Wires
plugin.tx_handlebars.view paths from the site
settings
handlebars.view.templateRootPath and
handlebars.view.partialRootPath. Required for every site
that renders Handlebars templates.
cpsit/handlebars-content-element (optional)
Sets
lib.contentElement = HANDLEBARSTEMPLATE, replacing
the default Fluid base object. Include this set when all content
elements should use Handlebars rendering by default.
Configure template paths
Declare where your .hbs files are located. The simplest option is
TypoScript:
Each entry in
variables is processed as a TYPO3 content
object against the current content element record. The resulting values
are passed to the template alongside the automatically injected
data and
current variables.
Flush caches
After editing TypoScript, flush the TYPO3 page cache. After editing
Services.yaml, flush and rebuild the service container as well.
HANDLEBARSTEMPLATE is a custom content object type provided by
this extension. It compiles and renders a Handlebars template, resolving
template paths, processing variables, and registering assets — all from
TypoScript configuration.
Name of the template to render. The value is resolved as a filename
(without the .hbs extension) relative to the configured template
root paths. Exactly one of
templateName,
template,
or
file must be set.
Example
templateName = Header
# With stdWrap
templateName.field = tx_myext_template_name
Copied!
template
Type
string / stdWrap
Description
Inline Handlebars source used directly as the template. Useful for short
or dynamically constructed templates. Cannot be used together with
templateName or
file.
Example
template = <h1>{{header}}</h1>
Copied!
file
Type
string / stdWrap
Description
Absolute or
EXT:-relative path to a Handlebars template
file. Cannot be used together with
templateName or
template.
Template root paths for this content object. These are added to the
content object path provider with the highest priority (100), overriding
any TypoScript or service container paths for this rendering. Higher
numeric keys take precedence over lower ones.
The shorthand
partialRootPath (singular) sets a single
path at key 0.
variables
Type
array
Description
Variables passed to the template. Each entry is processed as a content
object against the current content element's data record. Simple
string values are passed through as-is; entries with a sub-array are
rendered via
ContentObjectRenderer::cObjGetSingle().
Two variable names are reserved and always available automatically:
Arbitrary key-value pairs passed to the template as the
settings
variable. Unlike
variables, entries are not processed as
content objects — values are used as plain strings.
Example
settings {
showDate = 1
dateFormat = d.m.Y
}
Copied!
In the template:
{{#if settings.showDate}}<time>{{formatDate date settings.dateFormat}}</time>{{/if}}
Copied!
dataProcessing
Type
array
Description
Standard data processors, executed after
variables are resolved.
Processors receive and return the
$processedData array. Any key added
by a processor is available as a template variable.
The extension provides three additional processors:
process-variables,
resolve-markers, and
unflatten-variable-names.
Example
dataProcessing {
10 = database-query
10 {
table = tx_myext_domain_model_item
as = items
}
20 = process-variables
20 {
as = items
merge = 1
variables {
label = TEXT
label.field = title
}
}
}
Copied!
See also
Data processors for documentation of the extension-specific
processors.
preProcessing
Type
array
Description
Data source aware processors executed before
variables
are processed. These can read from multiple data sources (content element
record, processed data, processor configuration) and modify the variable
set before content object rendering begins.
postProcessing
Type
array
Description
Data source aware processors executed after
variables have
been resolved and data processors have run, but before the template is
rendered.
assets
Type
array
Description
Registers JavaScript and CSS assets via TYPO3's AssetCollector API. Supports
four sub-keys:
javaScript,
inlineJavaScript,
css,
inlineCss.
Asset management for the complete assets configuration reference.
headerAssets
Type
content object
Description
Adds arbitrary markup to the page
<head>. The value is evaluated
as a content object and the result is passed to
PageRenderer::addHeaderData().
Example
headerAssets = TEXT
headerAssets.value = <link rel="stylesheet" href="/assets/styles.css">
Copied!
footerAssets
Type
content object
Description
Adds arbitrary markup before the closing
</body> tag. The value
is evaluated as a content object and the result is passed to
PageRenderer::addFooterData().
Example
footerAssets = TEXT
footerAssets.value = <script src="/assets/app.js"></script>
Copied!
stdWrap
Type
stdWrap
Description
Standard TYPO3 stdWrap processing applied to the final rendered output.
The extension provides three data processors that integrate with the standard
TypoScript
dataProcessing chain inside
HANDLEBARSTEMPLATE
content objects.
Processes a
variables configuration block — exactly like the
top-level
variables of
HANDLEBARSTEMPLATE — within
a data processor chain. This is most useful when combined with other processors
such as
database-query, allowing per-record variable processing.
Data sources
When resolving configuration values, the processor draws from four data
sources, tried in the order listed:
Data source identifier
Contains
contentObjectRenderer
Current record's field values
contentObjectConfiguration
Top-level
HANDLEBARSTEMPLATE config
processedData
Accumulated output from previous processors
processorConfiguration
This processor's own config block
This is why options like
table and
as can be set
by an outer processor and automatically picked up by a nested
process-variables without being repeated explicitly.
The
preProcessing and
postProcessing hooks receive
the same collection, so they have access to all four sources as well.
dataProcessing {
10 = database-query
10 {
table = tx_myext_domain_model_item
as = items
dataProcessing {
10 = process-variables
10 {
table = tx_myext_domain_model_item
as = item
variables {
title = TEXT
title.field = title
body = TEXT
body.field = bodytext
body.parseFunc < lib.parseFunc_RTE
}
}
}
}
}
Copied!
Properties
variables
Variables to process. Same syntax as the top-level
variables in
HANDLEBARSTEMPLATE.
table
Database table of the record to use as the data source for field
lookups. Defaults to the current content element table.
as
Target key in the processed data array. When set, the processed
variables are stored under this key. When omitted, the processed
variables replace (or merge into) the root of the processed data.
merge
Boolean. When
1 and
as is omitted,
the processed variables are merged into the existing processed data
rather than replacing it. When
as is set and the key
already holds an array, the processed variables are merged into that
array. Default:
0.
if
Standard TypoScript
if condition. When the condition
evaluates to false, the processor is skipped and the processed data
is returned unchanged.
preProcessing
Data source aware processors run before
variables are
processed.
postProcessing
Data source aware processors run after
variables are
processed.
Replaces marker-style keys (e.g.,
###NAV_ITEMS###) in the
processed data with the values stored under those keys. The typical pattern is
to use a marker as a named placeholder early in the chain — either as the
as target of a preceding processor or directly in a
variables entry — and then resolve all markers to clean variable
names in a final step. This keeps intermediate processors decoupled from the
variable names the template expects.
tt_content.my_element = HANDLEBARSTEMPLATE
tt_content.my_element {
templateName = MyElement
dataProcessing {
# Declare the expected output slots early using markers — these# names will become the final template variables10 = menu
10 {
as = ###mainNavigation###
levels = 2
}
20 = menu
20 {
as = ###footerLinks###
special = directory
special.value = 42
}
# Resolve all markers to clean variable names in one final step90 = resolve-markers
90.removeNonMatchingMarkers = 1
}
}
Copied!
After processing, the template receives
mainNavigation and
footerLinks as clean variable names. The markers at the top of
the chain serve as upfront documentation of what the template expects, while
the processors that follow fill those slots independently.
Properties
pattern
Regular expression used to identify marker keys. The first capture
group becomes the resolved variable name.
Default:
###(.*?)###
removeNonMatchingMarkers
Boolean. When
1, variable keys that still match the
marker pattern after resolution (i.e., no value was found for them)
are removed from the processed data. Default:
0.
Converts dot-separated flat variable names into nested arrays. This is useful
when other processors set their
as key to a dotted path,
representing the intended position in a nested data structure.
dataProcessing {
10 = menu
10 {
as = page.nav.mainMenu
}
20 = menu
20 {
as = page.nav.footerLinks
special = directory
special.value = 42
}
90 = unflatten-variable-names
}
Copied!
After processing, the template receives a nested
page object:
Named arguments passed from the template (e.g., {{greet name="Alice"}})
are available as
$options->hash['name']. Positional arguments
(e.g., {{greet "Alice"}}) must be declared as additional parameters
in the method signature after
$options:
The
RenderingContext can also be injected by type-hint — declare it
anywhere in the method signature before positional arguments and it is provided
automatically. It gives access to the current PSR-7 request
(
$context->getRequest()) and the full set of template variables
(
$context->getVariables()):
The current template scope is accessible via
$options->scope, and block
helpers can call
$options->fn() and
$options->inverse() to render
their inner blocks.
Implement the
\CPSIT\Typo3Handlebars\Renderer\Helper\Helper interface and
place the
#[AsHelper] attribute on the class. Because the class implements
the
Helper interface, the attribute automatically resolves to the
render
method:
The attribute can also be placed directly on a method, in which case the method
name is inferred automatically — no
method parameter needed. Dependency
injection works normally for all
#[AsHelper]-annotated classes:
There's no need to implement the
Helper interface, it only serves as a
low-barrier tool to easily get started with custom helper implementations. You
can also just do the following:
If you cannot use the attribute (e.g., for a third-party class), register the
helper explicitly in Services.yaml. Both
identifier and
method are required:
Dispatched immediately before a template is compiled. The event provides
read-only access to the rendering context and the renderer. Use it to
inspect the context (e.g., to log which template is about to be rendered)
or to trigger additional rendering via the renderer:
Dispatched after variables have been resolved and merged, immediately before
the compiled template is executed. The full variable set can be read and
modified:
The Handlebars extension integrates with TYPO3's Asset collector
to manage JavaScript and CSS assets in your frontend rendering. Assets are registered
directly through the
assets configuration of a
HANDLEBARSTEMPLATE
content object.
See also
Asset collector in the TYPO3 Core API reference —
covers best practices, CSP/nonce usage, priority, and general troubleshooting.
useNonce (boolean): Add CSP nonce attribute (default: 0).
Deprecated since TYPO3 v14 — use
csp instead.
Example
options {
priority = 1
csp = 1
}
Copied!
Developer corner
This section documents the extension points available to developers who need
to go beyond what TypoScript configuration alone provides. Each page covers
one or more interfaces: when to implement them, the contract they define, and
how to wire the implementation into the service container.
Implement the \CPSIT\Typo3Handlebars\Renderer\Renderer
interface to replace the entire rendering stack — for example to use a
different template engine, add a pre-render transformation, or wrap the
compiled output. The default implementation is
\CPSIT\Typo3Handlebars\Renderer\HandlebarsRenderer.
interfaceRenderer
Fully qualified name
\CPSIT\Typo3Handlebars\Renderer\Renderer
renderTemplate(RenderingContext $context)
Compile and render a template. The
RenderingContext carries the
template path or inline source, the current variable set, and the PSR-7
request.
param RenderingContext $context
The current rendering context.
returntype
string
renderPartial(RenderingContext $context)
Compile and render a partial.
param RenderingContext $context
The current rendering context.
returntype
string
Wiring the implementation
Register the custom renderer as the implementation of the
Renderer interface in your extension's Services.yaml:
Implement the
\CPSIT\Typo3Handlebars\Renderer\Template\TemplateResolver
interface to change how template and partial names are resolved to absolute file
paths — for example to support a different directory layout, an additional file
extension, or a database-driven path lookup. The default implementation is
\CPSIT\Typo3Handlebars\Renderer\Template\HandlebarsTemplateResolver.
\CPSIT\Typo3Handlebars\Renderer\Template\BaseTemplateResolver implements
supports() and provides protected helpers for normalizing root paths and
resolving filenames with
EXT: syntax. Extending it keeps
implementations concise.
Implement the
\CPSIT\Typo3Handlebars\DataProcessing\DataSource\DataSourceAwareProcessor
interface to run custom PHP logic during the
preProcessing or
postProcessing stages of a
process-variables
processor (or of
HANDLEBARSTEMPLATE directly). Unlike standard
TYPO3 data processors, implementations receive a
DataSourceCollection
that gives structured access to all four data sources available at that point
in the pipeline.
DataSourceCollection::resolve() searches the data sources in priority
order and returns the first match. Pass a specific
DataSource case to
restrict the lookup:
useCPSIT\Typo3Handlebars\DataProcessing\DataSource\DataSource;
// Search all sources (highest priority first)
$table = $collection->resolve('table');
// Search only the processor configuration
$table = $collection->resolve('table', DataSource::ProcessorConfiguration);
// Search two specific sources, in the given order
$table = $collection->resolve('table', [
DataSource::ProcessorConfiguration,
DataSource::ContentObjectConfiguration,
]);
Copied!
The four
DataSource cases are:
DataSource::ProcessorConfiguration — this processor's own config block
DataSource::ProcessedData — accumulated output from previous processors
DataSource::ContentObjectRenderer — current record's field values
The numeric keys control execution order when multiple processors are
registered. The class is instantiated via
GeneralUtility::makeInstance(), so constructor injection works
as normal.
PathProvider & VariableProvider
Two further interfaces allow contributing template paths and global variables
from PHP rather than from TypoScript or Services.yaml configuration.
Both are auto-registered via
#[AutoconfigureTag] and both use a
priority integer to control merge order.
Implement the
\CPSIT\Typo3Handlebars\Renderer\Template\Path\PathProvider
interface to contribute template and partial root paths programmatically — for
example when paths depend on the current site configuration or a value not
available at container compile time.
Higher priority values are merged last and therefore take precedence over lower
ones. The three built-in providers use 0 (
GlobalPathProvider), 50
(
TypoScriptPathProvider), and 100 (
ContentObjectPathProvider).
The class is picked up automatically because
PathProvider carries
#[AutoconfigureTag('handlebars.template_path_provider')]. No extra
Services.yaml entry is needed beyond standard autowiring.
VariableProvider
Implement the
\CPSIT\Typo3Handlebars\Renderer\Variables\VariableProvider
interface to inject variables into every template rendering without repeating
them in TypoScript. Common uses include a site-wide locale string, feature
flags, or shared navigation data.
Providers are merged in ascending priority order; a higher-priority provider
can overwrite keys from a lower-priority one. The built-in
GlobalVariableProvider uses priority 0.
Like
PathProvider, the class is picked up automatically via
#[AutoconfigureTag('handlebars.variable_provider')] on the interface.
Migration
This page lists required migration steps when upgrading to a new major version
of the extension.
Version 1.0.0
Version 1.0.0 replaces the previous PHP-class rendering model (DataProcessor /
DataProvider / Presenter) with the
HANDLEBARSTEMPLATE content
object. All rendering configuration moves to TypoScript; custom PHP classes are
no longer the entry point.
Removed classes and interfaces
The following classes and interfaces have been removed and have no replacement:
The Services.yaml tag approach still works and can be used if you
cannot modify the helper class (e.g., a third-party class).
Template path configuration
Template path configuration via Services.yaml and TypoScript remains
unchanged. In addition, paths can now also be set per-content-object directly
in
HANDLEBARSTEMPLATE:
Thanks for considering contributing to this extension! Since it is
an open source product, its successful further development depends
largely on improving and optimizing it together.
The development of this extension follows the official
TYPO3 coding standards.
To ensure the stability and cleanliness of the code, various code
quality tools are used and most components are covered with test
cases. In addition, we use DDEV
for local development. Make sure to set it up as described below. For
continuous integration, we use GitHub Actions.
git clone https://github.com/CPS-IT/handlebars.git
cd handlebars
Copied!
Now install all Composer dependencies:
composer install
Copied!
Analyze code
# All analyzers
composer analyze
# Specific analyzers
composer analyze:dependencies
Copied!
Check code quality
# All linters
composer lint
# Specific linters
composer lint:composer
composer lint:editorconfig
composer lint:php
composer lint:typoscript
# Fix all CGL issues
composer fix
# Fix specific CGL issues
composer fix:composer
composer fix:editorconfig
composer fix:php
composer fix:typoscript
# All static code analyzers
composer sca
# Specific static code analyzers
composer sca:php
Copied!
Run tests
# All tests
composer test# Specific tests
composer test:functional
composer test:unit
# All tests with code coverage
composer test:coverage
# Specific tests with code coverage
composer test:coverage:functional
composer test:coverage:unit
# Merge code coverage of all test suites
composer test:coverage:merge
Copied!
Code coverage reports are written to .Build/coverage. You can
open the last merged HTML report like follows:
open .Build/coverage/html/_merged/index.html
Copied!
Build documentation
# Rebuild and open documentation
composer docs
# Build documentation (from cache)
composer docs:build
# Open rendered documentation
composer docs:open
Ideally, your PR references an issue describing the problem
you're trying to solve. All described code quality tools are automatically
executed on each pull request for all currently supported PHP versions and TYPO3
versions.
Sitemap
Reference to the headline
Copy and freely share the link
This link target has no permanent anchor assigned.The link below can be used, but is prone to change if the page gets moved.