Extbase plugins 

HandlebarsViewFactory integrates Handlebars rendering into the Extbase MVC stack. It implements \TYPO3\CMS\Core\View\ViewFactoryInterface and is wired globally, so every Extbase controller that goes through the standard view factory mechanism automatically benefits from it without any code changes.

When the factory detects an Extbase request it reads the handlebars key from the plugin's TypoScript configuration and returns a HandlebarsView. If no handlebars key is present and the controller does not extend HandlebarsController, the factory falls back to the Fluid view.

TypoScript configuration 

Per-plugin Handlebars configuration lives under the handlebars key in the plugin's TypoScript configuration:

plugin.tx_myextension_myplugin {
    handlebars {
        default {
            templateRootPaths.10 = EXT:my_extension/Resources/Private/Templates/Handlebars
            partialRootPaths.10 = EXT:my_extension/Resources/Private/Partials/Handlebars
        }
    }
}
Copied!

Resolution keys 

The handlebars array supports four keys, resolved and merged from least to most specific so that narrower entries override broader ones:

Key Applies to
default All controllers in the plugin
<ControllerAlias> One controller, all actions
<ControllerAlias>::<actionName> One controller, one action
<ControllerFQCN> Controller matched by fully-qualified class name
plugin.tx_myextension_myplugin {
    handlebars {
        default {
            templateRootPaths.10 = EXT:my_extension/Resources/Private/Templates/Handlebars
            partialRootPaths.10 = EXT:my_extension/Resources/Private/Partials/Handlebars
        }
        Blog {
            templateRootPaths.20 = EXT:my_extension/Resources/Private/Templates/Blog
        }
        Blog::list {
            templateName = @blog-list
        }
    }
}
Copied!

The controller alias matches the value registered in ExtensionUtility::configurePlugin(). For the example above, BlogController would typically have alias Blog.

Properties 

Each resolution key accepts the same properties as a HANDLEBARSTEMPLATE content object:

Property Type Description
templateName string Template name or @-prefixed flat name. Defaults to <ControllerAlias>/<action>.
format string File extension. Defaults to hbs.
templateRootPaths array Additional template root paths.
partialRootPaths array Additional partial root paths.
variables array Extra variables passed to the template.

Default template name 

When no templateName is configured, the factory derives one automatically from the controller alias and action name:

<ControllerAlias>/<actionName>
Copied!

For BlogController::listAction() with alias Blog this resolves to Blog/list.hbs under the configured template root paths.

HandlebarsController 

For controllers you own, extend \CPSIT\Typo3Handlebars\Controller\HandlebarsController instead of ActionController. This guarantees Handlebars rendering even when no handlebars TypoScript key is present — the factory always returns a HandlebarsView for these controllers:

EXT:my_extension/Classes/Controller/BlogController.php
namespace Vendor\MyExtension\Controller;

use CPSIT\Typo3Handlebars\Controller\HandlebarsController;
use Psr\Http\Message\ResponseInterface;

final class BlogController extends HandlebarsController
{
    public function listAction(): ResponseInterface
    {
        $this->view->assign('posts', $this->postRepository->findAll());

        return $this->htmlResponse($this->renderView());
    }
}
Copied!

Fluid fallback 

During an incremental migration you may need to keep some actions on Fluid while others are already on Handlebars. Call delegateRendering() on the view to hand off to the underlying Fluid view for that action:

public function legacyAction(): ResponseInterface
{
    $this->view->assign('items', $this->repository->findAll());

    $content = $this->view instanceof HandlebarsView
        ? $this->view->delegateRendering()
        : $this->view->render();

    return $this->htmlResponse((string)$content);
}
Copied!