---
title: Efficiently Extending TYPO3 with Middleware
url: "https://b13.com/knowledge/efficiently-extending-typo3-with-middleware"
description: Learn about using middleware in TYPO3 to implement custom access rules. Includes two real-world examples, along with code and tips. 
image: "https://b13.com/fileadmin/_processed_/6/3/csm_Simulate_access_Header_2a53da7a78.png"
author: Daniel Goerz
date: 2020-03-06
modified: 2026-08-19
lastUpdated: 2026-08-19
---

# Efficiently Extending TYPO3 with Middleware

[ TYPO3 ](https://b13.com/knowledge/typo3)

 Efficiently Extending TYPO3 with Middleware
=============================================

![](https://b13.com/fileadmin/_processed_/d/e/csm_daniel_goerz_2eed6f34a2.jpg)Daniel Goerz

  18 March 2020

 [ RSS Feed ](https://b13.com/rss.xml)

  ![An orange LEGO brick is positioned above two green LEGO bricks, creating a bridge-like structure against a dark background.](https://b13.com/fileadmin/_processed_/6/3/csm_Simulate_access_Header_e783883ae6.webp)

At b13, we get excited when clients come to us with interesting challenges. Have you ever been asked to add functionality to your website that is not currently available in your CMS? Building middleware can be a useful approach in these cases. To give you an idea of what can be accomplished with a middleware, I will share two use cases from customer projects at b13. I hope these two examples provide you some inspiration and help you embrace all the new tools and possibilities that TYPO3 CMS brings to the table.

  **Disclaimer:** This article will not cover the basic concepts of middlewares or how to register custom middlewares on top of the existing stack. If you want to learn about the basics, please refer to the[ official documentation](https://docs.typo3.org/m/typo3/reference-coreapi/master/en-us/ApiOverview/RequestHandling/Index.html) as well as to the blog post [“PSR-15 Middlewares in TYPO3”](https://usetypo3.com/psr15-middleware-in-typo3.html).

  The [PHP Standard Recommendation 15 (PSR-15)](https://www.php-fig.org/psr/psr-15/), which improves and standardizes request and response processing by defining request handlers and middleware, was initially introduced to TYPO3 with version 9 LTS. Since then, TYPO3 ships with many types of middleware that are heavily used by the core, like the middleware “NormalizedParamsAttribute” that enriches the Request object or the “RedirectHandler” middleware that resolves and performs redirects. In addition, developers can create their own middleware and extend the core.

   Middleware Examples: b13 Client Use Cases
-------------------------------------------

The use cases that follow originated from client projects. I’d like to emphasize that these are real world requirements that we had to solve. They are not theoretical proofs of concept, but actual solutions now running in production. Both involve implementing custom access rules for specific sections of client websites. In the first example, access to static HTML files had to be restricted to only those website users with a valid login session. In the second, some sections of the client website not usually accessible to site visitors needed to be temporarily accessible through a publicly shareable URL.

We chose middleware-based resolutions to these challenges because we wanted to be as technically unobtrusive as possible and not disrupt other functionality within the CMS. Intercepting the middleware stack at the right point also allowed us to achieve the required results with minimal effort.

   Use Case 1: Restrict Access to Static Files
---------------------------------------------

You might be familiar with this scenario: Some parts of a TYPO3 website consist only of mere static HTML files that are not generated by TYPO3 but uploaded and updated independently. Those files are accessible for everyone, they are linked to each other and quite important for some users.

In this case, the customer needed to restrict the access to those HTML files for some TYPO3 frontend users. Users who are logged in are allowed to access the HTML files in question. Site visitors who are not logged in, however, should be redirected to the login page.
 A middleware solution would be an appropriate approach for such a requirement. A simplified implementation might look like this:

  ```
1<br></br>2<br></br>3<br></br>4<br></br>5<br></br>6<br></br>7<br></br>8<br></br>9<br></br>10<br></br>11<br></br>12<br></br>13<br></br>14<br></br>15<br></br>16<br></br>17<br></br>18<br></br>
```

```
<span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">process</span><span class="hljs-params">(ServerRequestInterface $request, RequestHandlerInterface $handler)</span>: <span class="hljs-title">ResponseInterface</span><br></br></span>{<br></br>    <span class="hljs-keyword">if</span> (!<span class="hljs-keyword">$this</span>->isMatchingRequest($request)) {<br></br>        <span class="hljs-keyword">return</span> $handler->handle($request);<br></br>    }<br></br><br></br>    $context = GeneralUtility::makeInstance(Context::class);<br></br>    <br></br>    $userAspect = $context->getAspect(<span class="hljs-string">'frontend.user'</span>);<br></br>    <span class="hljs-keyword">if</span> (!$userAspect->isLoggedIn()) {<br></br>         $targetUrl = $request->getUri()<br></br>            ->withPath(<span class="hljs-string">'/login/'</span>)<br></br>            ->withQuery(<span class="hljs-string">'referer='</span> . rawurlencode((string)$request->getUri()));<br></br>        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> RedirectResponse($targetUrl, <span class="hljs-number">401</span>);<br></br>    }<br></br><br></br>    <span class="hljs-keyword">return</span> <span class="hljs-keyword">$this</span>->generateResponse($request);<br></br>}<br></br>
```

  First, we check to see if the request is eligible to be handled by our middleware. Then, we check to see if the request is to one of the restricted, static HTML files. If this is not the case, we pass the request on to the regular CMS processes.

Next, we simply ask if the frontend user is authenticated and based on that information, we either redirect the not-yet-authenticated user to the login page (passing the originally requested URl as referrer so the user is redirected back there after login) or we generate the response and return it directly for the user to consume. The `generateResponse()` method looks like this:

  ```
1<br></br>2<br></br>3<br></br>4<br></br>5<br></br>6<br></br>7<br></br>8<br></br>9<br></br>10<br></br>11<br></br>12<br></br>13<br></br>14<br></br>15<br></br>16<br></br>17<br></br>18<br></br>19<br></br>20<br></br>21<br></br>22<br></br>23<br></br>24<br></br>25<br></br>26<br></br>
```

```
<span class="hljs-keyword">protected</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">generateResponse</span><span class="hljs-params">(ServerRequestInterface $request)</span>: <span class="hljs-title">ResponseInterface</span><br></br></span>{<br></br>    $content = file_get_contents(Environment::getPublicPath() . $request->getUri()->getPath());<br></br>    <span class="hljs-keyword">switch</span> (<span class="hljs-keyword">$this</span>->fileExtension) {<br></br>        <span class="hljs-keyword">case</span> <span class="hljs-string">'html'</span>:<br></br>            $contentType = <span class="hljs-string">'text/html; charset=utf-8'</span>;<br></br>            $content = mb_convert_encoding($content, <span class="hljs-string">'HTML-ENTITIES'</span>, <span class="hljs-string">'Windows-1252'</span>);<br></br>            <span class="hljs-keyword">break</span>;<br></br>        <span class="hljs-keyword">case</span> <span class="hljs-string">'pdf'</span>:<br></br>            $contentType = <span class="hljs-string">'application/pdf'</span>;<br></br>            <span class="hljs-keyword">break</span>;<br></br>        <span class="hljs-keyword">case</span> <span class="hljs-string">'jpg'</span>:<br></br>            $contentType = <span class="hljs-string">'image/jpeg'</span>;<br></br>            <span class="hljs-keyword">break</span>;<br></br>        <span class="hljs-keyword">case</span> <span class="hljs-string">'png'</span>:<br></br>        <span class="hljs-keyword">case</span> <span class="hljs-string">'gif'</span>:<br></br>        <span class="hljs-keyword">case</span> <span class="hljs-string">'jpg'</span>:<br></br>            $contentType = <span class="hljs-string">'image/'</span> . <span class="hljs-keyword">$this</span>->fileExtension;<br></br>            <span class="hljs-keyword">break</span>;<br></br>        <span class="hljs-keyword">default</span>:<br></br>            $contentType = <span class="hljs-string">'text/plain'</span>;<br></br>    }<br></br>    $response = <span class="hljs-keyword">new</span> Response();<br></br>    $response->getBody()->write($content);<br></br>    <span class="hljs-keyword">return</span> $response->withHeader(<span class="hljs-string">'Content-Type'</span>, $contentType);<br></br>}<br></br>
```

  To make this approach work, we had to make sure that every request to a static HTML file was routed through TYPO3. We achieved this on the web server level by redirecting every request to TYPO3’s `index.php` file.

After this, it is important to find the right place in TYPO3s core middleware stack to insert our custom middleware. We needed the front-end user authentication processed `(typo3/cms-frontend/authentication)` and the matching site resolved `(typo3/cms-frontend/site)`. As the site resolving happens after the authentication, we registered our middleware after site resolution.

That is pretty much it! With a custom middleware we embedded files that lived outside of TYPO3 into TYPO3s access control mechanisms. The approach is clean and fast since it creates the response itself very early in the middleware stack. This avoids the execution of most of TYPO3’s regular request processing by and leads to a very quick response time.

   Use Case 2: Simulate Backend User Access
------------------------------------------

In this project, the situation was as follows: A reasonably large TYPO3 installation with multiple languages was going to add another language—and the client had plans to add many more. Content in the new language was already in place, while the language itself was still disabled in the site configuration (preventing it from being displayed in the frontend) so that
 backend editors could visit the website in the newly added language through TYPO3’s standard preview functionality. However, an additional requirement then came up. The new language variant needed to be proofread, but by proofreaders who did not (and should not) have backend accounts.

We needed to generate temporary access to the new language of the site for selected visitors. For that purpose we created a backend module that generates preview URLs that can be sent out to the proofreaders. The URLs contained a hash parameter recognized and validated by the middleware. Given a valid hash, the middleware stores the hash in a cookie, and gives the user preview access to the relevant content in the new, yet-to-be activated language. Proofreaders visiting the site via the specially generated URLs can browse the site in the disabled language as if they had an authenticated backend session.

The middleware looks like this:

  ```
1<br></br>2<br></br>3<br></br>4<br></br>5<br></br>6<br></br>7<br></br>8<br></br>9<br></br>10<br></br>11<br></br>12<br></br>13<br></br>14<br></br>15<br></br>16<br></br>17<br></br>18<br></br>19<br></br>20<br></br>21<br></br>22<br></br>23<br></br>24<br></br>25<br></br>26<br></br>
```

```
<span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">process</span><span class="hljs-params">(ServerRequestInterface $request, RequestHandlerInterface $handler)</span>: <span class="hljs-title">ResponseInterface</span><br></br></span>{<br></br>    <span class="hljs-keyword">if</span> (<span class="hljs-keyword">$this</span>->context->getPropertyFromAspect(<span class="hljs-string">'backend.user'</span>, <span class="hljs-string">'isLoggedIn'</span>)) {<br></br>        <span class="hljs-keyword">return</span> $handler->handle($request);<br></br>    }<br></br>    $language = $request->getAttribute(<span class="hljs-string">'language'</span>, <span class="hljs-keyword">null</span>);<br></br>    <span class="hljs-keyword">if</span> (!$language <span class="hljs-keyword">instanceof</span> SiteLanguage) {<br></br>        <span class="hljs-keyword">return</span> $handler->handle($request);<br></br>    }<br></br>    <span class="hljs-keyword">if</span> ($language->isEnabled()) {<br></br>        <span class="hljs-keyword">return</span> $handler->handle($request);<br></br>    }<br></br>    $hash = <span class="hljs-keyword">$this</span>->findHashInRequest($request);<br></br>    <span class="hljs-keyword">if</span> (<span class="hljs-keyword">empty</span>($hash)) {<br></br>        <span class="hljs-keyword">return</span> $handler->handle($request);<br></br>    }<br></br>    <span class="hljs-keyword">if</span> (!<span class="hljs-keyword">$this</span>->verifyHash($hash, $language)) {<br></br>        <span class="hljs-keyword">return</span> $handler->handle($request);<br></br>    }<br></br>    <span class="hljs-comment">// If the GET parameter PreviewUriBuilder::PARAMETER_NAME is set, then a cookie is set for the next request</span><br></br>    <span class="hljs-keyword">if</span> ($request->getQueryParams()[PreviewUriBuilder::PARAMETER_NAME] ?? <span class="hljs-keyword">false</span>) {<br></br>        <span class="hljs-keyword">$this</span>->setCookie($hash, $request->getAttribute(<span class="hljs-string">'normalizedParams'</span>));<br></br>    }<br></br>    <span class="hljs-keyword">$this</span>->initializePreviewUser();<br></br>    <span class="hljs-keyword">return</span> $handler->handle($request);<br></br>}<br></br>
```

  First, we check that no backend user is currently logged in and that the current language is set and disabled. Next, we look for the hash parameter in the URL and in the cookies. If a hash is found we validate it in the database to make sure it hasn’t expired, and that the hash was generated for the currently requested language.

All these checks determine whether the current request is eligible for the middleware to do it’s thing. Requests not meeting all the specified conditions will simply be passed along to the next middleware in line. The few that do fulfil its requirements will be picked up by our middleware, which stores the validated hash in a cookie and initializes a preview user.

The preview user is initialized by a stripped-down version of the BackendUserAuthentication, with access only to the specified language, so that TYPO3 will render the content despite the language in questions being disabled for all other users. Therefore, we place our middleware in line before the `typo3/cms-frontend/page-resolver` middleware and after the `typo3/cms-frontend/site middleware` because we need the Site being already resolved to access the current language.

If you need to do something similar, we open sourced this functionality through b13’s GitHub account as the TYPO3 [“authorized\_preview”](https://github.com/b13/authorized-preview) extension.

  ###  Conclusion

If you need to intercept and modify request processing to add to or modify core functionality, middleware may be the way to go.

**Final tips:**

- If your middleware is only responsible in some cases, put the least expensive checks (in terms of computing time) first to decide whether the current request should be modified or passed on to the rest of the middleware stack.
- Install the sysext TYPO3/cms-lowlevel system extension to give yourself access to the overview of TYPO3’s middleware stacks in the configuration module.
- Determine where your middleware needs to be located in the backend or frontend middleware stacks. What needs to be executed before your custom implementation kicks in?
- Register your new middleware to fire off accordingly.

Standardized middleware can be plugged into every system that supports PSR-15 request and response processing—like TYPO3. Feel free to use and adapt our implementation and also share your own. We’d love to hear from you about how you adapted our solutions or what you did to come up with your own! Let’s build cool stuff—together!

If you want to learn about the basics of middleware, please refer to [the official TYPO3 documentation](https://docs.typo3.org/m/typo3/reference-coreapi/master/en-us/ApiOverview/RequestHandling/) as well as to my blog post [PSR-15 middleware in TYPO3](https://usetypo3.com/psr15-middleware-in-typo3.html).

   Useful TYPO3 Extensions, from b13 to You!
-------------------------------------------

 [ Take a look ](https://b13.com/useful-typo3-extensions-from-b13-to-you)

  ###  Find the official PSR-15 specifications here:

<https://www.php-fig.org/psr/psr-15/>

  ###  Written by:

 ![Bild von Daniel Goerz](https://b13.com/fileadmin/_processed_/d/e/csm_daniel_goerz_9a279a5f6a.webp)

Daniel has been an integrated presence in our workday since he joined us remotely from Berlin in 2018. When not engaged in his geeky hobbies, we usually find him helping someone somewhere. His essential tool: The Internet

 Daniel Goerz  Development

 [ more from Daniel Goerz ](https://b13.com/team/daniel-goerz)

  Related Articles
------------------

- ![Cartoon trophy character surrounded by hands giving thumbs up and a heart gesture, set against a gear-patterned background.](https://b13.com/fileadmin/_processed_/f/7/csm_T3ppy_Design_Kit_Headerbild_b51eb9dc31.webp)

    ###  T3ppy Gives TYPO3 a Friendly Face

     06 August 2026 | Florian “Flix” Keitgen

     TYPO3 is powerful—but it doesn’t have to feel impersonal. Meet T3ppy, our friendly companion for the backend.

     [ Read more: T3ppy Gives TYPO3 a Friendly Face ](https://b13.com/knowledge/t3ppy-design-kit)
- ![Gavel labeled "AI" on a circuit-patterned background with yellow stars, symbolizing regulation or legislation related to artificial intelligence in the EU.](https://b13.com/fileadmin/_processed_/1/0/csm_EUAIAct_Headerbild_510724b3de.webp)

    ###  AI Content in TYPO3: Labelling Needs Accountability

     02 August 2026 | Benni Mack

     The EU AI Act brings the origins of AI-generated content into focus. AI Label marks AI-generated and AI-edited content in TYPO3 and records who signed off the published version—for…

     [ Read more: AI Content in TYPO3: Labelling Needs Accountability ](https://b13.com/knowledge/ai-content-in-typo3-labelling-needs-accountability)
- ![A series of stylized figures in various poses, each with a distinctive orange hat, depict a progression from walking to standing still while looking at a phone, set against a purple grid background.](https://b13.com/fileadmin/_processed_/d/7/csm_QueuesDDEV_Headerbild_f640931350.webp)

    ###  Better scalability with decoupled queues: How to set up RabbitMQ with TYPO3

     10 April 2024 | Jochen Roth

     When built-in message transports hit their limits, RabbitMQ can provide TYPO3 with a scalable, robust message queue.

     [ Read more: Better scalability with decoupled queues: How to set up RabbitMQ with TYPO3 ](https://b13.com/knowledge/better-scalability-with-decoupled-queues-how-to-set-up-rabbitmq-with-typo3)
- ![A cartoon character resembling a shield gives a thumbs up in front of a computer interface with various menu options.](https://b13.com/fileadmin/_processed_/e/6/csm_Header_cda1f80173.webp)

    ###  Meet T3ppy: A New Era for Editorial Work in TYPO3

     01 April 2026 | Florian “Flix” Keitgen

     T3ppy and the AiM extension bring AI directly into the TYPO3 backend—enhancing SEO, content quality, and workflows with centralized control.

     [ Read more: Meet T3ppy: A New Era for Editorial Work in TYPO3 ](https://b13.com/knowledge/meet-t3ppy-a-new-era-for-editorial-work-in-typo3)
- ![Retro microphone illustration with a shopping cart icon in the background, symbolizing e-commerce or online shopping.](https://b13.com/fileadmin/_processed_/f/8/csm_Marketplace_Headerbild_ee1575614c.webp)

    ###  Why TYPO3 Needs a Marketplace for Products

     25 March 2026 | Florian “Flix” Keitgen

     A marketplace would make TYPO3 products more discoverable, easier to compare, and benefit both agencies and clients alike.

     [ Read more: Why TYPO3 Needs a Marketplace for Products ](https://b13.com/knowledge/why-typo3-needs-a-marketplace-for-products)
- ![A hand holding a magnifying glass over a laptop screen displaying a webpage with an image and text elements.](https://b13.com/fileadmin/_processed_/e/5/csm_BackendUserSection_Headerbild_6093c842fd.webp)

    ###  Unlocking TYPO3’s Hidden Gem: The Backend User Section (Doktype 6)

     05 March 2026 | David Steeb

     Discover TYPO3’s underrated doktype 6 (Backend User Section) for secure internal previews, editor training, and prototyping. Learn real use cases, common pitfalls like 403 errors…

     [ Read more: Unlocking TYPO3’s Hidden Gem: The Backend User Section (Doktype 6) ](https://b13.com/knowledge/backend-user-section-in-typo3-uses-pitfalls-helper)
- ![Two stylized web page designs featuring a user profile, image placeholders, and text sections, set against a light blue background.](https://b13.com/fileadmin/_processed_/2/0/csm_BackendPreview_Headerbild_b4c5799cfa.webp)

    ###  Backend Previews With a System—Why We Built EXT:backendpreviews

     09 February 2026 | David Steeb

     EXT:backendpreviews brings structure and consistency to content previews in the TYPO3 backend using Fluid templates, layouts, and partials.

     [ Read more: Backend Previews With a System—Why We Built EXT:backendpreviews ](https://b13.com/knowledge/backend-previews-with-a-system-why-we-built-extbackendpreviews)
- ![A human hand shakes a robotic hand against a purple background with heart patterns, symbolizing collaboration between humans and technology.](https://b13.com/fileadmin/_processed_/6/2/csm_AIbotsLoveMarkdown_Headerbild_72fe5820d6.webp)

    ###  The Internet Is No Longer Just for Humans—AI Bots Love Markdown

     28 January 2026 | Benni Mack

     Discover why most web traffic is now automated and how TYPO3’s structured content model prepares websites for humans, editors, and AI systems.

     [ Read more: The Internet Is No Longer Just for Humans—AI Bots Love Markdown ](https://b13.com/knowledge/the-internet-is-no-longer-just-for-humans-ai-bots-love-markdown)
- ![Several colorful web page mockups stacked together, showcasing different layouts and design elements against a purple background.](https://b13.com/fileadmin/_processed_/e/3/csm_CaminoTheme_Headerbild_d082598009.webp)

    ###  Camino—The Need for a Default Theme in TYPO3 Is Real

     27 January 2026 | Benni Mack

     With TYPO3 v14, Camino introduces a default theme that removes friction from first installs. Why this matters—and how TYPO3 laid the groundwork.

     [ Read more: Camino—The Need for a Default Theme in TYPO3 Is Real ](https://b13.com/core-insights/blog/camino-the-need-for-a-default-theme-in-typo3-is-real)
- ![Graphic featuring stylized representations of a tower with the text "T3CON25 Düsseldorf" prominently displayed in the center.](https://b13.com/fileadmin/_processed_/7/b/csm_T3CON2025_Headerbild_fc9c5cc53e.webp)

    ###  T3CON25—One Step Closer to the Future of Content Management

     10 December 2025 | Franzi Töpler

     T3CON25 showcased the future of TYPO3 with insights on TYPO3 v14, AI, digital sovereignty, and open-source solutions driving secure, modern web experiences.

     [ Read more: T3CON25—One Step Closer to the Future of Content Management ](https://b13.com/knowledge/shaping-the-future-of-content-management-t3con25)