b13 / rate-limiting
Rate Limiting for TYPO3 pages - Frontend request rate limiting middleware
Package info
Type:typo3-cms-extension
pkg:composer/b13/rate-limiting
Requires
- symfony/rate-limiter: ^7.3
- typo3/cms-core: ^13.4 || ^14.0
Requires (Dev)
- typo3/coding-standards: ^0.8
README
This TYPO3 extension provides a frontend middleware for rate limiting requests based on configurable rules.
What does it do?
In case your server or hoster cannot deal with Rate Limiting, but your
system is typically under heavy load, this extension is for you. It is based
on the symfony/rate-limiting component.
- IP-based rate limiting: Limit requests per IP address
- URL pattern filtering: Apply rate limiting only to specific URLs (e.g., search, forms)
- HTTP method filtering: Rate limit only specific HTTP methods (GET, POST, etc.)
- User-Agent filtering: Rate limit specific bots, crawlers, or API clients
- IP masking: Group requests by IP blocks (useful for proxies or shared IPs)
- Caching Framework storage: Uses TYPO3's Caching Framework for persistence
- Site-specific configuration: Configure different limits per TYPO3 site
- DB logging: Every blocked request is written to
tx_rate_limiting_log - Enrichment event: A PSR-14 event lets any extension add extra fields to the log entry
- Configurable 429 page: Uses the site's error handler for status 429 when configured
Installation
- Install the extension via TER or
composer req b13/rate-limiting - Activate the extension in the Extension Manager whe installed via TER.
- Configure via Site Settings (see Configuration section)
Configuration
Configuration is done via Site Sets.
Available Settings
rateLimiting.enabled
- Type: Boolean
- Default:
false - Description: Enable or disable rate limiting for this site
rateLimiting.limit
- Type: Integer
- Default:
100 - Description: Maximum number of requests allowed within the interval
rateLimiting.interval
- Type: String
- Default:
1 minute - Description: Time interval for the rate limit
- Examples:
30 seconds,1 minute,5 minutes,1 hour
rateLimiting.ipMaskLength
- Type: Integer
- Default:
0(full IP) - Description: Number of IP blocks to use for grouping
- Examples:
0: Full IP (192.168.1.100)2: First 2 blocks (192.168.0.0)3: First 3 blocks (192.168.1.0)
rateLimiting.urlPatterns
- Type: String list
- Default:
[](all requests) - Description: Only rate limit requests matching these URL patterns
- Examples:
tx_solr: Rate limit Solr search requeststx_form: Rate limit form submissions/api/: Rate limit API endpoints
rateLimiting.methods
- Type: String list
- Default:
[](all methods) - Description: Only rate limit specific HTTP methods
- Examples:
POST,PUT,DELETE
rateLimiting.userAgentPatterns
- Type: String list
- Default:
[](all user agents) - Description: Only rate limit requests with matching User-Agent headers
- Supports: Simple string matching and wildcard patterns (
*and?) - Examples:
Googlebot: Rate limit Google's crawlerbot: Rate limit any user agent containing "bot"*crawler*: Wildcard matching for any crawlercurl*: Rate limit curl and similar toolsPython-*: Rate limit Python HTTP clients
Example Configurations
Example 1: Rate limit search requests
settings: rateLimiting: enabled: true limit: 50 interval: '1 minute' urlPatterns: - 'tx_solr'
Example 2: Rate limit POST requests with IP grouping
settings: rateLimiting: enabled: true limit: 20 interval: '1 minute' ipMaskLength: 2 methods: - POST
Example 3: Rate limit form submissions
settings: rateLimiting: enabled: true limit: 10 interval: '5 minutes' urlPatterns: - 'tx_form' methods: - POST
Example 4: Rate limit bots and crawlers
settings: rateLimiting: enabled: true limit: 30 interval: '1 minute' userAgentPatterns: - 'bot' - 'crawler' - 'spider'
Example 5: Rate limit specific API clients
settings: rateLimiting: enabled: true limit: 100 interval: '1 hour' urlPatterns: - '/api/' userAgentPatterns: - 'curl*' - 'Python-*' - 'PostmanRuntime*'
Logging
DB logging is disabled by default. Enable it per site via the rateLimiting.logging setting:
settings: rateLimiting: enabled: true logging: true
Every request that hits the rate limit is then written to the tx_rate_limiting_log database table with
the following fields:
| Field | Description |
|---|---|
ip |
Remote IP address |
timestamp |
Unix timestamp of the blocked request |
time |
Human-readable datetime (Y-m-d H:i:s) |
domain |
Host from the request URI |
query |
JSON-encoded query parameters |
user_agent |
User-Agent header |
Log entries are automatically cleaned up after 90 days when TYPO3's Table garbage collection scheduler task is active.
Enriching log entries with custom fields
Before writing to the database the middleware dispatches a
B13\RateLimiting\Event\RateLimitExceededEvent. Any event listener can call
addLogField(string $key, mixed $value) to inject additional columns into the
log record. The column must already exist in the table (add it via your
extension's ext_tables.sql).
Example: store the visitor's country
- Extend the table in your extension's
ext_tables.sql:
CREATE TABLE tx_rate_limiting_log ( country varchar(12) default '' not null );
- Register an event listener (TYPO3 13+):
<?php declare(strict_types=1); namespace Vendor\MyExtension\EventListener; use B13\RateLimiting\Event\RateLimitExceededEvent; use TYPO3\CMS\Core\Attribute\AsEventListener; use TYPO3\CMS\Core\Http\NormalizedParams; #[AsEventListener] class EnrichRateLimitLogEntry { public function __invoke(RateLimitExceededEvent $event): void { $normalizedParams = $event->getRequest()->getAttribute('normalizedParams') ?? NormalizedParams::createFromRequest($event->getRequest()); // Resolve the country by whichever means fits your project, then: $event->addLogField('country', $this->resolveCountry($normalizedParams->getRemoteAddress())); } private function resolveCountry(string $ip): string { // your geo-IP lookup here return ''; } }
The listener is auto-registered through autoconfigure: true in Services.yaml and the
#[AsEventListener] attribute — no further configuration required.
Customising the 429 response
By default the middleware returns a plain TYPO3 error page for HTML requests and a JSON response
for requests that send Accept: application/json.
To serve a fully styled error page, configure a Page error handler for HTTP status 429 in the site module. The middleware will automatically delegate to that handler when it is present.
License
The extension is licensed under GPL v2+, same as the TYPO3 Core. For details see the LICENSE file in this repository.
Credits
This extension was created by Benni Mack in 2025 for b13 GmbH, Stuttgart.
Find more TYPO3 extensions we have developed that help us deliver value in client projects. As part of our work, we focus on testing and best practices to ensure long-term performance, reliability, and results in all our code.