How to Configure Error Pages and 404 Handling in TYPO3
A practical guide to TYPO3 site error handling: choosing between the Page and Fluid error handlers, wiring up 404 and 403 responses, and verifying the real status code.
What a missing error page actually costs
Every TYPO3 site that goes live without a configured error page looks the same the first time a visitor mistypes a URL: they get the bare system error page. No navigation, no branding, no search box, no path back into the site. Search engines get a page they cannot map to anything. Visitors get the impression the site is broken, and most of them leave.
The strange part is that the fix has been part of TYPO3 core since version 9. Site handling moved error pages from TypoScript hacks and .htaccess rules into the per-site configuration, where each site defines which handler answers which HTTP status code. The official documentation describes the mechanism clearly: error handling is configured at site level, it is dependent on the current site and language, and TYPO3’s ErrorController falls back to the default system page only when a site configures no handler at all.
In practice, the reason most sites still show the default page is not ignorance of the feature. It is that the configuration has a few non-obvious behaviors — which handler type to pick, how the Page handler caches its output, what a 403 needs that a 404 does not — and nobody tests the error page until a customer complains. This guide walks through the decisions in the order we make them on TYPO3 projects, with the config that results.
Where error handling lives
Error handling is defined in the site configuration file, config/sites/<site-key>/config.yaml, alongside the routes, languages and base URL. Each entry in the errorHandling array pairs two values:
errorCode— the HTTP status code this entry is responsible for, for example404. Any code can be defined; the special value0acts as a catch-all for every code that has no dedicated entry.errorHandler— the strategy that produces the response:Page(fetch the content of an existing page),Fluid(render a Fluid template), orPHP(a custom implementation class).
One important boundary is documented explicitly and worth repeating: site error handling answers HTTP error responses, not PHP exceptions. A fatal error in an extension, a broken database connection or a failed dependency injection setup is handled by TYPO3’s lower-level error and exception handling, and no errorHandling entry will make those look pretty. If your problem is a white page or a 500, this is the wrong guide — start with your logs.
It is also worth knowing who triggers these handlers. \TYPO3\CMS\Frontend\Controller\ErrorController builds the error response, and any extension that reports an error through it — for example a plugin that finds no record for the requested slug — is answered by your site configuration, not the extension’s own fallback. The same controller has a customErrorAction() for arbitrary status codes, so an extension can legitimately answer with a 429 (“too many requests”), and you can still configure a matching Fluid template for it. Requests that accept application/json even get a JSON body with the technical reason instead of rendered HTML.
Option 1: point the error at an existing page
The Page handler answers an error by fetching another page’s rendered output. The configuration is minimal:
errorHandling:
- errorCode: 404
errorHandler: Page
errorContentSource: 't3://page?uid=145'
The errorContentSource accepts either an external URL or a TYPO3 page link — the t3://page?uid= syntax you get from the link picker in the backend. TYPO3 fetches the page with an internal sub-request, so the output is rendered inside the same site and language context as the broken request.
This is the option editors like, because the 404 page is a normal page in the tree: they can maintain its content with the same page module they use everywhere else. Create a “Page not found” page, exclude it from menus and the sitemap, and point errorContentSource at it.
Two documented caveats shape how we use it:
The output is cached. The rendered error page is stored in the TYPO3 page cache, and the documentation is blunt about why: it is a safeguard against denial-of-service attacks, since error pages can be requested at a very high rate by simply generating random URLs. The consequence is that anything dynamic on the error page — an uncached plugin, user-specific content — gets frozen into the cached copy. The official guidance is that administrators must verify no sensitive data (the documentation’s example: a logged-in frontend user’s name) ends up on a cached error page, and that genuinely dynamic error pages require a custom PHP handler instead.
The source page has to survive on its own. Because the content comes from a real page, that page has to be reachable in the frontend for the site’s current language. If someone hides the error page, moves it outside the site root, or locks it behind a frontend login, the error response degrades. Keep the page visible, exclude it from menus, and treat “hidden” as a property of the layout — not of the page itself.
Option 2: render a Fluid template
The Fluid handler renders a template file directly, without touching the page tree:
errorHandling:
- errorCode: 404
errorHandler: Fluid
errorFluidTemplate: 'EXT:my_sitepackage/Resources/Private/Templates/Sites/Error.fluid.html'
errorFluidTemplatesRootPath: ''
errorFluidLayoutsRootPath: ''
errorFluidPartialsRootPath: 'EXT:my_sitepackage/Resources/Private/Partials/Sites/'
All four keys are documented options: errorFluidTemplate points at the template file, and the three root-path keys (errorFluidTemplatesRootPath, errorFluidLayoutsRootPath, errorFluidPartialsRootPath) let you reuse the layouts and partials of your site package instead of building a fully standalone page. Paths can be absolute, relative to the site root, or — the usual choice — EXT: references into your site package.
This is our default for 404 pages, for three reasons:
- It is version-controlled. The error page ships with the site package and is reviewed like any other template. A redesign updates it automatically; nobody has to remember a page that is excluded from all menus.
- It has no runtime dependencies. There is no page that can be hidden, no content element that can be deleted by an over-enthusiastic editor, no sub-request that depends on the state of the page tree.
- It can lean on the design system. With the partials root path set, the error page uses the same header, footer and navigation partials as the rest of the site — which is the entire point: a 404 should look like the site, not like an apology.
The template itself is ordinary Fluid. A minimal starting point:
<f:layout name="Default" />
<f:section name="Main">
<div class="error-page">
<h1>404 — Page not found</h1>
<p>The page you are looking for does not exist or has moved.</p>
<p><a href="/">Back to the homepage</a></p>
</div>
</f:section>
The configuration we actually ship
For a typical public site, we configure three entries:
errorHandling:
- errorCode: 403
errorHandler: Page
errorContentSource: 't3://page?uid=12'
- errorCode: 404
errorHandler: Fluid
errorFluidTemplate: 'EXT:my_sitepackage/Resources/Private/Templates/Sites/Error.fluid.html'
- errorCode: 0
errorHandler: Fluid
errorFluidTemplate: 'EXT:my_sitepackage/Resources/Private/Templates/Sites/Error.fluid.html'
The 403 entry is the one most often missing, and it matters for any site with a member area. When a visitor requests a page their frontend user group does not allow, TYPO3 answers with a 403. Sending them to a “page not found” template is both wrong and wasteful — the page exists, they are just not logged in. Pointing the 403 at the login page (here page 12) turns a dead end into the beginning of a login flow.
The trailing errorCode: 0 entry is a cheap insurance policy. Every status code without a dedicated handler — a 410, a 429 thrown by an extension via customErrorAction(), anything unexpected — gets your branded page instead of the system default. The two 404-oriented lines could be collapsed into this one, but an explicit 404 entry documents intent and gives you the option to differentiate later.
Test it the way crawlers see it
An error page is one of the few features you should test after launch, on the production domain, because its behavior depends on the full routing, caching and language context. Three checks, two minutes:
curl -I https://www.example.com/definitely-not-a-page/
Check the status line, not the page. The response must be a real HTTP/1.1 404. TYPO3’s ErrorController sets the status correctly, but this is where reverse proxies, custom rewrite rules or a well-meaning “catch everything and show the homepage” configuration silently break things. A 404 that answers 200 is worse than an ugly error page: search engines will index duplicate content, and the soft-404 handling in Search Console eventually flags it.
Repeat per language. Error handling is language-dependent, as the documentation notes. Add the language prefix and test https://www.example.com/de/definitely-not-a-page/ too. A common failure after a new translation is added: the site configuration is fine, but the error page used by the Page handler does not exist in the new language.
Check the wrong-language case on a single-language site too — a request to /de/… on a site with no German can produce a different response than a plain 404, depending on language fallback configuration.
Pitfalls we keep seeing
Redirects are checked first. If a URL used to exist and the slug changed, the redirect — the sys_redirect records managed in the redirects module — answers before any error handling runs. Before debugging a “404 that should redirect”, look in the redirects module and confirm no redirect exists; the ordering and the record format are covered in our guide to managing redirects in TYPO3. The corollary matters just as much: do not “fix” 404s by creating redirects to the homepage. Redirect the URL to a real equivalent page or let it 404 — mass redirects to the front page are a known soft-404 pattern.
Verify after every major upgrade. Site configuration is stable across minor releases, but a major upgrade rewrites enough of the routing and site-handling layer that the two-minute curl test belongs on the checklist. Our TYPO3 v12 to v13 upgrade guide treats error pages as a launch-gate item precisely because they are invisible until they break.
Do not confuse the backend and the frontend. A site whose pages all render fine in the page module but 404 in the frontend is usually a routing or site-configuration problem — missing root page, wrong entry point, a language misconfiguration — not an error-handling problem. Error handling only decides what the failure looks like; it never fixes it.
Closing the loop
A branded, correctly-statused error page is one of those details that separates a finished site from a template installation: small effort, permanent payoff, and fully covered by core — no extension needed. The workflow above (Fluid handler for 404, login page for 403, catch-all for everything else, curl check after every deployment) is what we ship on client sites as standard, and it is included in the routine of our TYPO3 development services — along with the redirect hygiene and upgrade discipline linked above, which together keep old URLs, error pages and content working as one system.
Official sources.
Use these references to verify platform-specific behavior and version details.
Continue with the surrounding work.
How to Upgrade a TYPO3 v12 Site to v13 LTS
How to Fix the WordPress Media Upload HTTP Error for Good
Broader context from the YCTheme blog.
B2B Ecommerce Platform Selection: Buy the Workflow, Not the Feature List
B2B buying runs on account pricing, quotes, net terms and ERP integration. A requirements-first look at Shopify, WooCommerce and TYPO3.
Staging Environments: How Professional Teams Ship CMS Changes Without Breaking Production
Staging is where risky changes prove themselves before customers see them. A practical look at how WordPress, Joomla, TYPO3 and Shopify teams stage, test and promote changes.