
Symfony:rate Limiting Skill
- 396 installs
- 190 repo stars
- Updated August 6, 2026
- makfly/superpowers-symfony
symfony:rate-limiting is an agent skill that configures Symfony RateLimiter policies and controller attributes so developers can throttle REST endpoints, login routes, and webhooks without blocking legitimate API clients
About
symfony:rate-limiting is a Claude Code agent skill from makfly/superpowers-symfony that guides Symfony developers through RateLimiter setup for REST endpoints, login throttling, and webhook protection. The skill implements sliding window, token bucket, and fixed window policies in config/packages/rate_limiter.yaml and applies the #[RateLimit] controller attribute so exceeded limits return HTTP 429 with Retry-After headers. It also covers async Messenger, Scheduler, and Cache workflows where handlers must respect downstream rate limits through idempotent handlers, retry policies, and failure-transport observability under at-least-once delivery. Developers reach for symfony:rate-limiting when hardening Symfony APIs against abuse while keeping legitimate clients unblocked, especially within the Superpowers Symfony plugin that targets Symfony 7.4 LTS and 8.x. The workflow inspects async contracts and routing, updates limiter configuration, and outputs operational validation evidence for replay and recovery scenarios.
- Symfony RateLimiter component setup
- Per-route and per-user policies
- Redis or cache storage backends
- Custom limiter factories and keys
- Graceful 429 responses and headers
Symfony:Rate Limiting by the numbers
- 396 all-time installs (skills.sh)
- Ranked #1,096 of 4,492 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Aug 11, 2026 (Skillselion catalog sync)
npx skills add https://github.com/makfly/superpowers-symfony --skill symfonyrate-limitingAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 396 |
|---|---|
| repo stars | ★ 190 |
| Last updated | August 6, 2026 |
| Repository | makfly/superpowers-symfony ↗ |
How do you add Symfony rate limiters to API endpoints?
Add Symfony rate limiters to REST endpoints, login routes, and webhooks to throttle abuse without breaking legitimate API clients.
Who is it for?
Symfony backend developers implementing API abuse protection on Symfony 7.4 LTS or 8.x services with REST, login, or webhook endpoints.
Skip if: Developers on non-Symfony PHP stacks or teams that only need CDN-level DDoS protection without application-layer throttling.
When should I use this skill?
User asks to rate-limit Symfony REST endpoints, login routes, webhooks, or Messenger handlers calling downstream APIs.
What you get
Updated rate_limiter.yaml, protected controller routes, retry/failure policy notes, and operational validation evidence.
- rate_limiter.yaml configuration
- Protected controller routes
- Operational validation evidence
By the numbers
- Supports 3 Symfony RateLimiter policies: sliding window, token bucket, and fixed window
- Parent Superpowers Symfony plugin targets Symfony 7.4 LTS and 8.x
Files
Rate Limiting (Symfony)
Use when
- Implementing asynchronous workflows with Messenger/Scheduler/Cache.
- Stabilizing retries and failure transports.
Default workflow
1. Define async contract and delivery semantics. 2. Implement idempotent handlers and routing strategy. 3. Configure retries, failure transport, and observability. 4. Validate success/failure replay scenarios.
Guardrails
- Assume at-least-once delivery, not exactly-once.
- Keep handlers deterministic and side-effect aware.
- Surface poison-message handling strategy.
Progressive disclosure
- Use this file for execution posture and risk controls.
- Open references when deep implementation details are needed.
Output contract
- Async config/handlers updated.
- Retry/failure policy decisions.
- Operational validation evidence.
References
reference.mddocs/complexity-tiers.md
Reference
Symfony Rate Limiting
Installation
composer require symfony/rate-limiterConfiguration
# config/packages/rate_limiter.yaml
framework:
rate_limiter:
# Anonymous API requests
anonymous_api:
policy: sliding_window
limit: 100
interval: '1 hour'
# Authenticated API requests
authenticated_api:
policy: sliding_window
limit: 1000
interval: '1 hour'
# Login attempts
login:
policy: fixed_window
limit: 5
interval: '15 minutes'
# Contact form
contact_form:
policy: fixed_window
limit: 3
interval: '1 hour'
# Expensive operations
export:
policy: token_bucket
limit: 10
rate: { interval: '1 hour', amount: 5 }Rate Limiting Algorithms
Fixed Window
Simple count within time window:
login:
policy: fixed_window
limit: 5
interval: '15 minutes'Sliding Window
Smoother rate limiting, prevents burst at window edges:
api:
policy: sliding_window
limit: 100
interval: '1 hour'Token Bucket
Allows bursts while maintaining average rate:
export:
policy: token_bucket
limit: 10 # Bucket size (max burst)
rate:
interval: '1 hour' # Refill interval
amount: 5 # Tokens added per intervalPolicies
- fixed_window — simplest; allows bursts at window edges.
- sliding_window —
75% * previous_window_hits + current_hits. The
anchor_at option (8.1+ — verify) aligns windows to a calendar instant.
- token_bucket — tokens refilled at a rate; tolerates bursts up to bucket size.
- compound — combines several limiters; all must accept.
framework:
rate_limiter:
contact_form:
policy: compound
limiters: [two_per_minute, five_per_hour]
rolling_api:
policy: sliding_window
limit: 100
interval: '1 hour'
anchor_at: '00:00' # 8.1+ — verifyUsing Rate Limiters
As a controller attribute (8.1+ — verify)
The simplest path: declare the named limiter on the action. Returns 429 Too Many Requests with a Retry-After header automatically.
use Symfony\Component\ExpressionLanguage\Expression;
use Symfony\Component\HttpKernel\Attribute\RateLimit;
#[RateLimit('api')] // default key = IP + method + path
public function index(): JsonResponse {}
#[RateLimit('api', methods: ['POST', 'PUT', 'PATCH', 'DELETE'])]
public function edit(): JsonResponse {}
#[RateLimit('per_account', key: new Expression('request.request.get("email")'))]
public function resetPassword(): Response {}
#[RateLimit('per_account', key: fn (array $args, Request $request): string => $request->query->get('email'))]
public function resetViaLink(): Response {}
#[RateLimit('api', tokens: 5)] // consume 5 tokens
public function export(): JsonResponse {}Stack multiple #[RateLimit] attributes — all must pass.
In Controllers (injected service)
Typehint RateLimiterFactoryInterface (not the concrete RateLimiterFactory) and select the named limiter with #[Target]:
<?php
// src/Controller/ApiController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\DependencyInjection\Attribute\Target;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\RateLimiter\RateLimiterFactoryInterface;
use Symfony\Component\Routing\Attribute\Route;
class ApiController extends AbstractController
{
public function __construct(
#[Target('authenticated_api')]
private RateLimiterFactoryInterface $authenticatedApiLimiter,
#[Target('anonymous_api')]
private RateLimiterFactoryInterface $anonymousApiLimiter,
) {}
#[Route('/api/data', methods: ['GET'])]
public function getData(Request $request): Response
{
// Choose limiter based on authentication
$limiter = $this->getUser()
? $this->authenticatedApiLimiter->create($this->getUser()->getUserIdentifier())
: $this->anonymousApiLimiter->create($request->getClientIp());
$limit = $limiter->consume();
if (!$limit->isAccepted()) {
return new JsonResponse(
['error' => 'Too many requests. Please try again later.'],
Response::HTTP_TOO_MANY_REQUESTS,
[
'X-RateLimit-Remaining' => $limit->getRemainingTokens(),
'X-RateLimit-Retry-After' => $limit->getRetryAfter()->getTimestamp(),
'Retry-After' => $limit->getRetryAfter()->getTimestamp() - time(),
]
);
}
// Add rate limit headers
$response = new JsonResponse(['data' => '...']);
$response->headers->set('X-RateLimit-Remaining', $limit->getRemainingTokens());
$response->headers->set('X-RateLimit-Limit', $limit->getLimit());
return $response;
}
}In Services
<?php
// src/Service/ExportService.php
namespace App\Service;
use Symfony\Component\RateLimiter\RateLimiterFactory;
class ExportService
{
public function __construct(
private RateLimiterFactory $exportLimiter,
) {}
public function export(User $user): string
{
$limiter = $this->exportLimiter->create($user->getId());
$limit = $limiter->consume();
if (!$limit->isAccepted()) {
throw new TooManyRequestsException(
'Export limit reached. Please wait.',
$limit->getRetryAfter()
);
}
return $this->generateExport($user);
}
}Login Rate Limiting
<?php
// src/Security/LoginRateLimiter.php
namespace App\Security;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\RateLimiter\RateLimiterFactory;
use Symfony\Component\Security\Http\RateLimiter\AbstractRequestRateLimiter;
class LoginRateLimiter extends AbstractRequestRateLimiter
{
public function __construct(
private RateLimiterFactory $loginLimiter,
) {}
protected function getLimiters(Request $request): array
{
// Rate limit by IP + username combination
$username = $request->request->get('_username', '');
$ip = $request->getClientIp();
return [
$this->loginLimiter->create($ip),
$this->loginLimiter->create($username . $ip),
];
}
}Configure in security:
# config/packages/security.yaml
security:
firewalls:
main:
form_login:
login_path: login
check_path: login
login_throttling:
limiter: loginEvent Subscriber for Global Rate Limiting
<?php
// src/EventSubscriber/RateLimitSubscriber.php
namespace App\EventSubscriber;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\RateLimiter\RateLimiterFactory;
class RateLimitSubscriber implements EventSubscriberInterface
{
public function __construct(
private RateLimiterFactory $apiLimiter,
) {}
public static function getSubscribedEvents(): array
{
return [
KernelEvents::REQUEST => ['onRequest', 10],
];
}
public function onRequest(RequestEvent $event): void
{
$request = $event->getRequest();
// Only rate limit API routes
if (!str_starts_with($request->getPathInfo(), '/api/')) {
return;
}
$limiter = $this->apiLimiter->create($request->getClientIp());
$limit = $limiter->consume();
if (!$limit->isAccepted()) {
$event->setResponse(new JsonResponse(
['error' => 'Rate limit exceeded'],
Response::HTTP_TOO_MANY_REQUESTS,
['Retry-After' => $limit->getRetryAfter()->getTimestamp() - time()]
));
}
}
}Reserve Tokens (Blocking)
Wait for tokens instead of rejecting:
$limiter = $this->exportLimiter->create($user->getId());
// Will block until token is available (max 30 seconds)
$reservation = $limiter->reserve(1, 30);
// Wait for the reservation
$reservation->wait();
// Proceed with rate-limited operation
$this->generateExport($user);Testing
<?php
use Symfony\Component\RateLimiter\RateLimiterFactory;
use Symfony\Component\RateLimiter\Storage\InMemoryStorage;
class RateLimitTest extends TestCase
{
public function testRateLimitEnforced(): void
{
// Create limiter with in-memory storage for testing
$factory = new RateLimiterFactory([
'id' => 'test',
'policy' => 'fixed_window',
'limit' => 3,
'interval' => '1 minute',
], new InMemoryStorage());
$limiter = $factory->create('user_123');
// First 3 requests should succeed
for ($i = 0; $i < 3; $i++) {
$this->assertTrue($limiter->consume()->isAccepted());
}
// 4th request should fail
$this->assertFalse($limiter->consume()->isAccepted());
}
}Best Practices
1. Different limits by role: More for authenticated users 2. Compound keys: IP + user for login attempts 3. Return headers: X-RateLimit-Remaining, Retry-After 4. Sliding window for APIs - smoother limiting 5. Token bucket for burst tolerance 6. Redis storage for distributed systems
Skill Operating Checklist
Design checklist
- Confirm operation boundaries and invariants first.
- Minimize scope while preserving contract correctness.
- Test both happy path and negative path behavior.
Validation commands
- php bin/console messenger:consume --limit=1
- php bin/console messenger:failed:show
- ./vendor/bin/phpunit --filter=Messenger
Failure modes to test
- Invalid payload or forbidden actor.
- Boundary values / not-found cases.
- Retry or partial-failure behavior for async flows.
Related skills
How it compares
Pick symfony:rate-limiting over generic security skills when you need Symfony-native RateLimiter YAML, #[RateLimit] attributes, and Messenger-aware throttling in one workflow.
FAQ
What Symfony rate limiter policies does symfony:rate-limiting support?
symfony:rate-limiting supports Symfony's sliding window, token bucket, and fixed window RateLimiter policies configured in framework.rate_limiter YAML and enforced via the #[RateLimit] controller attribute or injected RateLimiterFactory services.
Does symfony:rate-limiting cover Symfony Messenger workflows?
symfony:rate-limiting covers Messenger, Scheduler, and Cache async workflows by guiding idempotent handlers, retry and failure-transport configuration, and downstream rate limits before external API calls.