konradmichalik / typo3-routing
Attribute Routing - Register frontend endpoints via PHP attributes on controller methods, as an attribute-based alternative to the missing frontend counterpart of AjaxRoutes.php.
Package info
github.com/konradmichalik/typo3-routing
Type:typo3-cms-extension
pkg:composer/konradmichalik/typo3-routing
Requires
- php: ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0
- psr/container: ^1.1 || ^2.0
- psr/http-message: ^1.0 || ^2.0
- psr/http-server-handler: ^1.0.2
- psr/http-server-middleware: ^1.0.2
- symfony/console: ^7.0 || ^8.0
- symfony/dependency-injection: ^7.0 || ^8.0
- symfony/rate-limiter: ^7.0 || ^8.0
- symfony/routing: ^7.0 || ^8.0
- typo3/cms-core: ^13.4 || ^14.0
- typo3/cms-frontend: ^13.4 || ^14.0
- typo3fluid/fluid: ^4.2 || ^5.0
Requires (Dev)
- eliashaeussler/version-bumper: ^4.0.3
- phpunit/phpunit: ^11.0 || ^12.0 || ^13.0
- typo3/cms-base-distribution: ^13.4 || ^14.0
- typo3/cms-lowlevel: ^13.4 || ^14.0
- typo3/testing-framework: ^8.2 || ^9.0 || ^10.0
This package is auto-updated.
Last update: 2026-06-29 08:24:07 UTC
README
TYPO3 extension typo3_routing
This extension lets you register frontend endpoints via PHP attributes on controller methods — the attribute-based counterpart to the backend-only Configuration/Backend/AjaxRoutes.php. It is response-format agnostic: return JSON, HTML, XML, or a download.
Warning
This package is in early development stage and may change significantly in the future. I am working steadily to release a stable version as soon as possible.
Note
The goal is a familiar, Symfony-Routing-like developer experience: declare a frontend endpoint with a single #[Route] attribute instead of wiring a custom middleware and duplicating the path across PHP and JavaScript.
✨ Features
- Attribute routing — declare an endpoint with
#[Route]directly on a controller method - Typed arguments — methods receive type-cast path/query/body values, no manual request reading
- Zero-config discovery — routes are collected at container compile time, no extra cache
- URL generation — a Fluid ViewHelper so the path lives once, not duplicated as a PHP constant and a JS string
- Opt-in caching — cache responses with
#[Cache], with tag-based invalidation - Opt-in rate limiting — throttle requests per client IP with
#[RateLimit] - Opt-in authentication & CSRF — protect routes with
#[Authenticate](bearer token / FE / BE user) and#[RequireRequestToken] - Debug command — list every registered route as a table or JSON, including an
--unprotectedaudit
🔥 Installation
Requirements
- TYPO3 >= 13.4
- PHP 8.2+
Composer
composer require konradmichalik/typo3-routing
TER
Download the zip file from TYPO3 extension repository (TER).
🚀 Quick start
Implement RouteControllerInterface, register the controller as a service, and annotate a public method with #[Route]:
use KonradMichalik\Typo3Routing\Attribute\Route; use KonradMichalik\Typo3Routing\Routing\RouteControllerInterface; use Psr\Http\Message\ResponseInterface; use TYPO3\CMS\Core\Http\JsonResponse; final readonly class CourseSearchController implements RouteControllerInterface { #[Route(path: '/api/course-search/count', name: 'course_search_count')] public function count(): ResponseInterface { return new JsonResponse(['count' => 42]); } }
That's it — GET /api/course-search/count now returns your JSON.
Everything else is opt-in on top of that. A route can take typed arguments, validate input, cache its response, and throttle clients — all declared with attributes, the controller stays plain:
use Symfony\Component\Routing\Requirement\Requirement; #[Route(path: '/api/courses/{id}', name: 'course_show', requirements: ['id' => Requirement::DIGITS])] #[Cache(lifetime: 3600, tags: ['tx_courses_domain_model_course'])] #[RateLimit(limit: 60, interval: '1 minute')] public function show(int $id, int $page = 1): ResponseInterface { // $id ← path placeholder, cast to int (404 if not digits) // $page ← ?page=… query param, defaults to 1 return new JsonResponse(/* … */); }
Protecting a route is just as declarative — require a logged-in frontend user (or a bearer token / BE user, OR-combined):
#[Route(path: '/api/account', name: 'account')] #[Authenticate(FrontendUserAuthenticator::class)] public function account(): ResponseInterface { // Reached only when a frontend user is logged in — 401 otherwise. return new JsonResponse(/* … */); }
See Usage for the full #[Route] reference and typed arguments.
📚 Documentation
| Topic | What's inside |
|---|---|
| Usage | The #[Route] attribute, requirements, and typed controller arguments |
| URL Generation | routing:uri / routing:uris Fluid ViewHelpers and the PHP generator |
| Configuration | Path prefix gate, environment-bound routes, middleware placement |
| Caching | Opt-in response caching with #[Cache] and tag-based invalidation |
| Rate Limiting | Opt-in per-IP throttling with #[RateLimit] |
| Authentication & CSRF | Protecting routes with #[Authenticate], request tokens, and deployment notes |
| How It Works | Compile-time discovery, runtime dispatch, and the routing:debug command |
| How It Compares | When to reach for this vs. AjaxRoutes, custom middleware, eID, or Extbase plugins |
🧑💻 Contributing
Please have a look at CONTRIBUTING.md.
⭐ License
This project is licensed under GNU General Public License 2.0 (or later).