Building Custom Typo3 Extensions with Extbase and Fluid: A Practical Guide
Learn how to build clean, maintainable Typo3 extensions using Extbase MVC and Fluid templates. This practical guide covers separation of concerns, repository patterns, and site-specific configuration with a worked example.
02 Jan 2026, 12:52 UTC

The Challenge: Clean Separation in Typo3 Extensions
When building custom extensions for Typo3, developers often struggle with maintaining clean code organization. Mixing database logic with presentation code leads to extensions that are hard to debug, maintain, and hand off to frontend designers. The traditional approach of embedding PHP directly in templates creates a tangled web where changes in one area unexpectedly break another.
Extbase: The MVC Foundation
Extbase implements the Model-View-Controller pattern, providing a structured way to organize your extension code. Models represent your domain data, Controllers handle business logic and orchestrate the flow, and Views (typically Fluid templates) render the output. This separation means you can modify database queries without touching your HTML, or redesign templates without understanding SQL.
The key insight is that Extbase isn't just a framework—it's a discipline. Each component has a single responsibility, making your extension predictable and testable.
Fluid Templates: Designer-Friendly Markup
Fluid provides a declarative template syntax that keeps HTML and PHP logic distinct. Instead of writing PHP echo statements throughout your templates, you use clean XML-like tags. This allows frontend designers to work on templates without needing to understand your PHP domain objects.
Consider this Fluid snippet that iterates over news items:
<f:for each="{newsItems}" as="item">
<h3>{item.title}</h3>
<p>{item.description -> f:format.html()}</p>
</f:for>
Repositories: Type-Safe Data Access
The Repository pattern in Extbase abstracts database queries behind type-safe methods. When you inject a repository into your controller, you're not dealing with raw SQL—you're calling methods that return domain objects. Typo3's caching framework integrates automatically, so well-designed repositories can dramatically improve performance.
Here's a repository method that finds published news items:
public function findPublished(): QueryResultInterface {
$query = $this->createQuery();
return $query->matching(
$query->equals('published', 1)
)->execute();
}
Configuration: Site-Specific Control
Page TSconfig and TypoScript modules let you inject configuration on a per-page basis. Need to disable a feature on specific pages? Need different template paths for different sections of your site? These configuration layers provide granular control without touching core code.
A typical TypoScript setup might look like:
plugin.tx_mynews {
view {
templateRootPath = EXT:my_news/Resources/Private/Templates/
partialRootPath = EXT:my_news/Resources/Private/Partials/
layoutRootPath = EXT:my_news/Resources/Private/Layouts/
}
}
Worked Example: Building a Simple News Extension
Let's walk through creating a minimal news extension. Start by creating the directory structure:
typo3conf/ext/my_news/
Classes/Domain/Model/News.php
Classes/Domain/Repository/NewsRepository.php
Classes/Controller/NewsController.php
Resources/Private/Templates/News/List.html
Configuration/
Your domain model lives in Classes/Domain/Model/News.php, the repository in Classes/Domain/Repository/NewsRepository.php, and the controller in Classes/Controller/NewsController.php. The Fluid template goes in Resources/Private/Templates/News/List.html.
The controller action is elegantly simple:
public function listAction(): \TYPO3\CMS\Extbase\Mvc\ResponseInterface {
$this->view->assign('newsItems', $this->newsRepository->findPublished());
return $this->htmlResponse();
}
Note: exact method signatures vary between TYPO3 LTS versions (for example, response handling changed around TYPO3 v11). Verify against the release notes for your target version.
Trade-offs and Limitations
Extbase isn't without overhead. Each request involves reflection-based dependency injection and, depending on version, annotation parsing. For high-traffic sites, this can add milliseconds to response times. Always profile with TYPO3's caching enabled in a production-like environment, and consider bypassing Extbase for performance-critical endpoints.
Complex Fluid templates can become difficult to debug. The declarative syntax is powerful, but errors often surface as generic template-resolution messages rather than helpful line numbers. Keep templates lean and use partials for reusable markup—they're easier to test in isolation.
Version compatibility matters. Extensions built against one TYPO3 LTS may break when upgraded. Always verify against the current LTS release notes before deployment.
Actionable Next Steps
Start small: scaffold a minimal Extbase extension using the Extension Builder module, then gradually replace generated code with your own implementations. Test repository methods in isolation, and always verify caching is active in production-like environments by checking query logs in dev mode.
Toggle a Page TSconfig or TypoScript setting to confirm your extension respects site-specific overrides—for example, disabling the plugin on a specific page and confirming the content element no longer renders. Run TYPO3's unit test tooling for your extension before deployment; these tests catch many integration issues early.
The investment in proper Extbase architecture pays dividends in maintainability. Your extension becomes a well-organized, designer-friendly component that can evolve with your site's needs.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.