Laravel API Rate-Limiting: Custom Limiters &amp; Headers | Mohamed Said        [  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MH.png)   Mohamed Said Laravel Backend Engineer  ](https://www.msaied.com) [ Home ](https://www.msaied.com) [ Projects ](https://www.msaied.com/projects) [ Articles  ](https://www.msaied.com/articles) [ Certificates ](https://www.msaied.com/certificates) [ Contact ](https://www.msaied.com#contact-section) 

       [  ](https://github.com/EG-Mohamed)       

 [ Home ](https://www.msaied.com) [ Projects ](https://www.msaied.com/projects) [ Articles ](https://www.msaied.com/articles) [ Certificates ](https://www.msaied.com/certificates) [ Contact ](https://www.msaied.com#contact-section) 

  [ home ](https://www.msaied.com)    [ articles ](https://www.msaied.com/articles)    Laravel API Rate-Limiting: Custom Limiters, Per-Route Strategies, and Header Contracts        On this page       1. [  Beyond throttle:60,1 ](#beyond-codethrottle601code)
2. [  Defining Named Limiters in a Service Provider ](#defining-named-limiters-in-a-service-provider)
3. [  Attaching Limiters to Routes ](#attaching-limiters-to-routes)
4. [  Consistent Response Headers ](#consistent-response-headers)
5. [  Handling 429 Responses Gracefully ](#handling-429-responses-gracefully)
6. [  Testing Limiters with Pest ](#testing-limiters-with-pest)
7. [  Key Takeaways ](#key-takeaways)

  ![Laravel API Rate-Limiting: Custom Limiters, Per-Route Strategies, and Header Contracts](https://cdn.msaied.com/474/47b5edd1bda5625b01ae20f7684ed199.png)

  #laravel   #api   #rate-limiting   #middleware   #pest  

 Laravel API Rate-Limiting: Custom Limiters, Per-Route Strategies, and Header Contracts 
========================================================================================

     27 Jul 2026      4 min read    ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said  

       Table of contents

1. [  01   Beyond throttle:60,1  ](#beyond-codethrottle601code)
2. [  02   Defining Named Limiters in a Service Provider  ](#defining-named-limiters-in-a-service-provider)
3. [  03   Attaching Limiters to Routes  ](#attaching-limiters-to-routes)
4. [  04   Consistent Response Headers  ](#consistent-response-headers)
5. [  05   Handling 429 Responses Gracefully  ](#handling-429-responses-gracefully)
6. [  06   Testing Limiters with Pest  ](#testing-limiters-with-pest)
7. [  07   Key Takeaways  ](#key-takeaways)

 Beyond `throttle:60,1`
----------------------

The built-in `throttle` middleware is fine for a quick demo, but production APIs need rate-limiting that reflects business rules: free-tier users get 100 requests/minute, paid users get 2 000, and certain endpoints — like password reset — have their own hard caps regardless of tier.

Laravel's `RateLimiter` facade, introduced in Laravel 8 and refined since, gives you exactly that control.

---

Defining Named Limiters in a Service Provider
---------------------------------------------

Register all limiters in `AppServiceProvider::boot` (or a dedicated `RateLimitServiceProvider`).

```php
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\RateLimiter;

public function boot(): void
{
    // Tier-aware API limiter
    RateLimiter::for('api', function (Request $request) {
        $user = $request->user();

        if (! $user) {
            return Limit::perMinute(30)->by($request->ip());
        }

        return match ($user->plan) {
            'enterprise' => Limit::none(),
            'pro'        => Limit::perMinute(2000)->by($user->id),
            default      => Limit::perMinute(100)->by($user->id),
        };
    });

    // Hard cap on auth-sensitive endpoints
    RateLimiter::for('auth-sensitive', function (Request $request) {
        return [
            Limit::perMinute(5)->by($request->ip()),
            Limit::perHour(20)->by($request->ip()),
        ];
    });
}

```

Returning an **array** of `Limit` objects lets you stack multiple windows on a single route — both must pass.

---

Attaching Limiters to Routes
----------------------------

```php
// routes/api.php
Route::middleware(['auth:sanctum', 'throttle:api'])
    ->group(function () {
        Route::get('/widgets', [WidgetController::class, 'index']);
    });

Route::post('/forgot-password', [PasswordController::class, 'store'])
    ->middleware('throttle:auth-sensitive');

```

The string passed to `throttle:` maps directly to the name registered with `RateLimiter::for`.

---

Consistent Response Headers
---------------------------

Laravel automatically adds `X-RateLimit-Limit`, `X-RateLimit-Remaining`, and `Retry-After` when a limiter is hit. But for clients that need to *proactively* back off, you want those headers on every response, not just 429s.

Add a middleware that reads the current hit count without incrementing it:

```php
namespace App\Http\Middleware;

use Closure;
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\RateLimiter;
use Symfony\Component\HttpFoundation\Response;

class AddRateLimitHeaders
{
    public function handle(Request $request, Closure $next, string $limiterName = 'api'): Response
    {
        $response = $next($request);

        $limiter = RateLimiter::limiter($limiterName);
        /** @var Limit $limit */
        $limit = value($limiter, $request);

        if ($limit instanceof Limit) {
            $key       = $limit->key;
            $maxAttempts = $limit->maxAttempts;
            $remaining = max(0, $maxAttempts - RateLimiter::attempts($key));

            $response->headers->set('X-RateLimit-Limit', $maxAttempts);
            $response->headers->set('X-RateLimit-Remaining', $remaining);
        }

        return $response;
    }
}

```

Register it after `throttle` in the middleware stack so it runs on successful responses too.

---

Handling 429 Responses Gracefully
---------------------------------

The default 429 response is an HTML page. Override it in your exception handler:

```php
// bootstrap/app.php (Laravel 11+)
->withExceptions(function (Exceptions $exceptions) {
    $exceptions->render(function (
        \Illuminate\Http\Exceptions\ThrottleRequestsException $e,
        Request $request
    ) {
        return response()->json([
            'error'       => 'rate_limit_exceeded',
            'retry_after' => $e->getHeaders()['Retry-After'] ?? null,
        ], 429, $e->getHeaders());
    });
})

```

Passing `$e->getHeaders()` ensures `Retry-After` and `X-RateLimit-*` headers survive the custom renderer.

---

Testing Limiters with Pest
--------------------------

```php
use Illuminate\Support\Facades\RateLimiter;

it('blocks free-tier users after 100 requests per minute', function () {
    $user = User::factory()->create(['plan' => 'free']);

    // Exhaust the limit without real HTTP overhead
    RateLimiter::hit('100|' . $user->id, 60, 100);

    $this->actingAs($user)
        ->getJson('/api/widgets')
        ->assertStatus(429)
        ->assertJsonFragment(['error' => 'rate_limit_exceeded']);
});

it('does not throttle enterprise users', function () {
    $user = User::factory()->create(['plan' => 'enterprise']);

    $this->actingAs($user)
        ->getJson('/api/widgets')
        ->assertOk();
});

```

`RateLimiter::hit` lets you seed the counter directly, avoiding 100 actual HTTP calls in your test suite.

---

Key Takeaways
-------------

- Use `RateLimiter::for` with closures to express business-tier logic, not magic middleware strings.
- Return an array of `Limit` objects to enforce multiple windows (per-minute **and** per-hour) simultaneously.
- Always emit `X-RateLimit-*` headers on successful responses so clients can self-throttle.
- Override the 429 renderer to return JSON with the `Retry-After` header intact.
- Seed `RateLimiter::hit` in tests to simulate exhausted limits without real HTTP loops.

 Found this useful?

          [  ](https://twitter.com/intent/tweet?url=https%3A%2F%2Fwww.msaied.com%2Farticles%2Flaravel-api-rate-limiting-custom-limiters-per-route-strategies-and-header-contracts-1&text=Laravel+API+Rate-Limiting%3A+Custom+Limiters%2C+Per-Route+Strategies%2C+and+Header+Contracts) [  ](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fwww.msaied.com%2Farticles%2Flaravel-api-rate-limiting-custom-limiters-per-route-strategies-and-header-contracts-1) 

 Frequently Asked Questions 
----------------------------

  3 questions  

     Q01  Can I use a database or Redis key other than the default cache store for rate limiters?        Yes. Laravel's RateLimiter uses the default cache store, but you can swap it by binding a custom RateLimiter instance in the service container that points to a specific cache driver, or by configuring your default cache to Redis in production — which is the recommended approach for distributed deployments. 

      Q02  How do I reset a user's rate-limit counter programmatically, for example after a plan upgrade?        Call `RateLimiter::clear($key)` where `$key` matches the string returned by your limiter closure (e.g. `'100|' . $user-&gt;id`). This removes the counter from the cache immediately, giving the user a fresh window. 

      Q03  Does returning `Limit::none()` for enterprise users skip the throttle middleware entirely?        Yes. `Limit::none()` signals the throttle middleware to allow unlimited requests. No cache key is written and no headers are added, so there is zero overhead for those users. 

  Continue reading

 More Articles 
---------------

 [ View all    ](https://www.msaied.com/articles) 

 [ ![Profiling Laravel with Blackfire and Xdebug: Finding Real Bottlenecks](https://cdn.msaied.com/473/5b2261450fb7c3c68870997e338c2e1b.png) laravel performance profiling 

### Profiling Laravel with Blackfire and Xdebug: Finding Real Bottlenecks

Stop guessing where your Laravel app is slow. Learn how to use Blackfire and Xdebug profilers together to pinp...

  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said 

 26 Jul 2026     4 min read  

  Read    

 ](https://www.msaied.com/articles/profiling-laravel-with-blackfire-and-xdebug-finding-real-bottlenecks-1) [ ![Filament v4 Migrating from v3: Breaking Changes and Refactor Patterns](https://cdn.msaied.com/472/bb7fbf8441c775fcfadd23850e1756e8.png) filament laravel migration 

### Filament v4 Migrating from v3: Breaking Changes and Refactor Patterns

A practical, opinionated guide to the most impactful Filament v3→v4 breaking changes: schema-based forms, the...

  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said 

 26 Jul 2026     4 min read  

  Read    

 ](https://www.msaied.com/articles/filament-v4-migrating-from-v3-breaking-changes-and-refactor-patterns) [ ![Concurrency in Laravel: Process Pools, Fibers, and the Concurrency Facade](https://cdn.msaied.com/471/ba1a628c196e92cbb81033c865c9ad26.png) laravel concurrency php 

### Concurrency in Laravel: Process Pools, Fibers, and the Concurrency Facade

Laravel's Concurrency facade and process pools let you run independent work in parallel without reaching for a...

  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said 

 26 Jul 2026     4 min read  

  Read    

 ](https://www.msaied.com/articles/concurrency-in-laravel-process-pools-fibers-and-the-concurrency-facade) 

   [  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MH.png)   Mohamed Said Laravel Backend Engineer  ](https://www.msaied.com)Senior Backend Engineer specializing in Laravel, scalable SaaS platforms, APIs, and cloud infrastructure. I build secure, high-performance web applications that help businesses grow.

Explore

- [Home](https://www.msaied.com)
- [Projects](https://www.msaied.com/projects)
- [Articles](https://www.msaied.com/articles)
- [Certificates](https://www.msaied.com/certificates)
- [Contact](https://www.msaied.com#contact-section)

Connect

- [   hello@msaied.com ](mailto:hello@msaied.com)
- [   +20 109 461 9204 ](tel:+201094619204)

© 2026 Mohamed Said. All rights reserved.

 [  ](https://github.com/EG-Mohamed) [  ](https://www.linkedin.com/in/msaiedm/) [  ](https://wa.me/201094619204) [  ](mailto:hello@msaied.com) [  ](https://drive.google.com/file/u/0/d/1MF20IPRJyzfy32mhEutjL5EpSls0w2Q8/view)
