---
title: "Gradual migration"
manual: "Handlebars"
version: "main"
permalink: "https://docs.typo3.org/permalink/cpsit/typo3-handlebars:migration-from-fluid-gradual@main"
source: "Guides/MigrationFromFluid/GradualMigration.rst"
rendered: "2026-09-29T09:05:57+00:00"
---

# Gradual migration {#migration-from-fluid-gradual}

Replacing every Fluid template in one step is rarely practical. This page
describes an incremental approach that lets Fluid and Handlebars coexist in
the same TYPO3 installation — even within the same extension — so you can
migrate one content element or controller at a time.

## Strategy overview {#migration-from-fluid-gradual-overview}

The recommended path has three lanes that can be worked independently:

-   **Content elements** rendered via `tt_content.*` TypoScript: Replace
    the content object type from `FLUIDTEMPLATE` to
    `HANDLEBARSTEMPLATE` one CType at a time.
-   **Extbase controllers**: Extend `HandlebarsController`, then migrate templates
    action by action with a Fluid fallback in place.
-   **Shared partials / layout**: Migrate the layout shell last, once all
    templates that depend on it have been converted.

1.  Install and configure paths

    Install the extension and include the base site set without the
    content-element set. The content-element set replaces
    `lib.contentElement` globally; omitting it keeps all existing content
    elements on Fluid.

    **config/sites/\<site>/config.yaml**

    ```yaml
    dependencies:
      - cpsit/handlebars   # base set only — does NOT touch lib.contentElement
    ```

    Configure template and partial root paths for Handlebars independently of
    the Fluid paths:

    ```typoscript
    plugin.tx_handlebars {
        view {
            templateRootPaths {
                10 = EXT:my_sitepackage/Resources/Private/Templates/Handlebars
            }
            partialRootPaths {
                10 = EXT:my_sitepackage/Resources/Private/Partials/Handlebars
            }
        }
    }
    ```

    Fluid and Handlebars path registrations are completely independent, so
    there is no risk of one system picking up the other's files.
1.  Migrate content elements one at a time

    For each content element, convert the TypoScript definition from
    `FLUIDTEMPLATE` to `HANDLEBARSTEMPLATE` and create
    the corresponding `.hbs` file.

    **Before (Fluid):**

    ```typoscript
    tt_content.tx_myext_teaser = FLUIDTEMPLATE
    tt_content.tx_myext_teaser {
        templateName = Teaser
        templateRootPaths.10 = EXT:my_extension/Resources/Private/Templates
        partialRootPaths.10 = EXT:my_extension/Resources/Private/Partials
        layoutRootPaths.10 = EXT:my_extension/Resources/Private/Layouts
        variables {
            title = TEXT
            title.field = header
        }
        dataProcessing {
            10 = files
            10 {
                references.fieldName = image
                as = images
            }
        }
    }
    ```

    **After (Handlebars):**

    ```typoscript
    tt_content.tx_myext_teaser = HANDLEBARSTEMPLATE
    tt_content.tx_myext_teaser {
        templateName = Teaser
        templateRootPaths.10 = EXT:my_extension/Resources/Private/Templates/Handlebars
        partialRootPaths.10 = EXT:my_extension/Resources/Private/Partials/Handlebars
        variables {
            title = TEXT
            title.field = header
        }
        dataProcessing {
            10 = files
            10 {
                references.fieldName = image
                as = images
            }
        }
    }
    ```

    All other `tt_content.*` definitions that are not yet migrated
    continue to use Fluid without any changes.
1.  Migrate Extbase controllers

    For controller-based rendering, the extension provides
    `HandlebarsViewFactory`, which replaces TYPO3's default
    `FluidViewFactory`. It inspects the TypoScript configuration and returns
    a `HandlebarsView` when Handlebars configuration is present, or falls
    back to the Fluid view when it is not.

    > [!NOTE]
    > The view factory is globally injected to all Extbase controllers extending
    > `ActionController`, so there's no need to switch the view factory injection.

    **Extend HandlebarsController:**

    For controllers you own, extend
    `\CPSIT\Typo3Handlebars\Controller\HandlebarsController` instead of
    `ActionController`. The `renderView()` method renders the current
    action via Handlebars:

    **EXT:my_extension/Classes/Controller/BlogController.php**

    ```php
    namespace Vendor\Extension\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());
        }
    }
    ```

    The view factory resolves the template name automatically from the
    controller alias and action name (e.g., `Blog/list.hbs` for
    `listAction`). Configure paths and per-action overrides via the
    `handlebars` key in the plugin's TypoScript namespace:

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

    > [!NOTE]
    > **See also**
    >
    > [Extbase plugins](https://docs.typo3.org/permalink/cpsit/typo3-handlebars:extbase-plugin@main) for the full configuration reference, including
    > per-controller and per-action overrides.

    **Using the Fluid fallback during migration:**

    When a controller action has not yet been ported, call
    `delegateRendering()` on the view to delegate to the underlying Fluid
    view. This keeps the action working while the template is being migrated:

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

        // Render with Fluid until the .hbs template is ready
        $content = $this->view instanceof HandlebarsView
            ? $this->view->delegateRendering()
            : $this->view->render();

        return $this->htmlResponse((string)$content);
    }
    ```
1.  Migrate the page layout shell

    The layout shell (the outer HTML document, header, navigation, footer) is
    typically the last thing to migrate because it is shared by every page.
    Keep the existing Fluid layout in place until all content elements and
    actions that reference it have been converted to Handlebars partials.

    Once all consumers are migrated, convert the Fluid layout to a Handlebars
    layout partial using the `extend` / `block` /
    `content` pattern described in [Layouts and partials](https://docs.typo3.org/permalink/cpsit/typo3-handlebars:migration-from-fluid-layouts@main).
1.  Switch the lib.contentElement default

    After every content element has been migrated to `HANDLEBARSTEMPLATE`,
    include the `cpsit/handlebars-content-element` site set. This sets
    `lib.contentElement = HANDLEBARSTEMPLATE` globally so that
    newly created content elements start from Handlebars automatically:

    **config/sites/\<site>/config.yaml**

    ```yaml
    dependencies:
      - cpsit/handlebars
      - cpsit/handlebars-content-element
    ```

> [!NOTE]
> **See also**
>
> -   [Quick start](https://docs.typo3.org/permalink/cpsit/typo3-handlebars:quick-start@main) — minimal working setup from scratch
> -   [Layouts and partials](https://docs.typo3.org/permalink/cpsit/typo3-handlebars:migration-from-fluid-layouts@main) — layout and section migration
> -   [Helpers](https://docs.typo3.org/permalink/cpsit/typo3-handlebars:migration-from-fluid-helpers@main) — porting ViewHelpers
