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: Real API Rate-Limiting in Laravel ](#beyond-codethrottle601code-real-api-rate-limiting-in-laravel)
2. [  Registering Named Limiters ](#registering-named-limiters)
3. [  Attaching Limiters to Routes ](#attaching-limiters-to-routes)
4. [  Understanding the Response Headers ](#understanding-the-response-headers)
5. [  Programmatic Checking Inside Business Logic ](#programmatic-checking-inside-business-logic)
6. [  Clearing Limits on Successful Upgrade ](#clearing-limits-on-successful-upgrade)
7. [  Testing Rate Limiters with Pest ](#testing-rate-limiters-with-pest)
8. [  Key Takeaways ](#key-takeaways)

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

  #laravel   #api   #rate-limiting   #pest   #performance  

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

     16 Jun 2026      3 min read    ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said  

       Table of contents

1. [  01   Beyond throttle:60,1: Real API Rate-Limiting in Laravel  ](#beyond-codethrottle601code-real-api-rate-limiting-in-laravel)
2. [  02   Registering Named Limiters  ](#registering-named-limiters)
3. [  03   Attaching Limiters to Routes  ](#attaching-limiters-to-routes)
4. [  04   Understanding the Response Headers  ](#understanding-the-response-headers)
5. [  05   Programmatic Checking Inside Business Logic  ](#programmatic-checking-inside-business-logic)
6. [  06   Clearing Limits on Successful Upgrade  ](#clearing-limits-on-successful-upgrade)
7. [  07   Testing Rate Limiters with Pest  ](#testing-rate-limiters-with-pest)
8. [  08   Key Takeaways  ](#key-takeaways)

 Beyond `throttle:60,1`: Real API Rate-Limiting in Laravel
---------------------------------------------------------

The built-in `throttle` middleware is fine for a quick guard, but production APIs need more: per-plan quotas, per-endpoint budgets, graceful degradation, and headers clients can actually parse. Laravel's `RateLimiter` facade gives you all of that — most teams just never reach for it.

---

### Registering Named Limiters

Define 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
{
    RateLimiter::for('api', function (Request $request) {
        $user = $request->user();

        if ($user?->plan === 'enterprise') {
            return Limit::none(); // unlimited
        }

        return Limit::perMinute($user?->rate_limit ?? 60)
            ->by($user?->id ?? $request->ip());
    });

    RateLimiter::for('search', function (Request $request) {
        return [
            Limit::perMinute(30)->by($request->user()?->id ?? $request->ip()),
            Limit::perDay(5_000)->by($request->user()?->id ?? $request->ip()),
        ];
    });
}

```

Returning an **array** of `Limit` objects enforces multiple windows simultaneously — a per-minute burst cap *and* a daily quota. Laravel checks all of them; the first exceeded limit triggers the 429.

---

### Attaching Limiters to Routes

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

Route::middleware('auth:sanctum', 'throttle:search')
    ->get('/search', [SearchController::class, 'index']);

```

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

---

### Understanding the Response Headers

When a request is allowed, Laravel automatically appends:

```yaml
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 43

```

When the limit is hit (HTTP 429):

```yaml
Retry-After: 47
X-RateLimit-Reset: 1718023847

```

`Retry-After` is in **seconds**; `X-RateLimit-Reset` is a Unix timestamp. Well-behaved clients should honour `Retry-After` before retrying. Document this contract explicitly — it saves support tickets.

---

### Programmatic Checking Inside Business Logic

Sometimes you need to gate an expensive operation *inside* a controller or action, not at the routing layer.

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

class GenerateReportAction
{
    public function execute(User $user): Report
    {
        $key = 'report-generation:' . $user->id;

        if (RateLimiter::tooManyAttempts($key, maxAttempts: 5)) {
            $seconds = RateLimiter::availableIn($key);
            throw new TooManyRequestsException(
                "Try again in {$seconds} seconds."
            );
        }

        RateLimiter::hit($key, decaySeconds: 3600);

        return Report::generate($user);
    }
}

```

`RateLimiter::hit()` increments the counter and sets the TTL on first hit. `availableIn()` returns the seconds until the window resets — pipe that into your API error payload so clients can back off intelligently.

---

### Clearing Limits on Successful Upgrade

When a user upgrades their plan mid-session, stale limits can frustrate them. Clear programmatically:

```php
RateLimiter::clear('report-generation:' . $user->id);

```

This is also useful in tests — reset state between feature test cases rather than waiting for cache TTLs.

---

### Testing Rate Limiters with Pest

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

it('returns 429 after exceeding the search limit', function () {
    $user = User::factory()->create();

    // Exhaust the limiter
    foreach (range(1, 30) as $_) {
        actingAs($user)->getJson('/api/search?q=test');
    }

    $response = actingAs($user)->getJson('/api/search?q=test');

    $response->assertStatus(429)
        ->assertHeader('Retry-After');
});

beforeEach(fn () => RateLimiter::clear('search:' . auth()->id()));

```

Always clear the limiter in `beforeEach` — cache bleeds between tests otherwise.

---

### Key Takeaways

- Use `RateLimiter::for()` with named limiters instead of raw `throttle:N,M` for any non-trivial API.
- Return an array of `Limit` objects to enforce burst *and* daily quotas simultaneously.
- `Retry-After` and `X-RateLimit-Reset` are your client contract — document them.
- `RateLimiter::hit()` / `tooManyAttempts()` / `availableIn()` let you gate expensive operations inside business logic, not just at the HTTP layer.
- Clear limiters in Pest's `beforeEach` to prevent cache state leaking between tests.

 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&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) 

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

  3 questions  

     Q01  Can I apply multiple rate limit windows (e.g. per-minute and per-day) to the same route?        Yes. Return an array of Limit objects from your RateLimiter::for() callback. Laravel evaluates all of them and triggers a 429 as soon as any single limit is exceeded. 

      Q02  How do I prevent rate limiter state from leaking between Pest feature tests?        Call RateLimiter::clear('your-key') in a beforeEach block. Because limiters are backed by the cache driver, they persist across requests in the same test run unless explicitly cleared. 

      Q03  Does returning Limit::none() for enterprise users skip all header injection?        Yes. When Limit::none() is returned, the throttle middleware treats the request as unlimited and does not append X-RateLimit-* headers, so clients should not rely on those headers being present for all users. 

  Continue reading

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

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

 [ ![Laravel Telescope Alternatives: Building a Lightweight Request Inspector with Middleware](https://cdn.msaied.com/216/9b6d240010b80483f072902dafcd216c.png) laravel middleware debugging 

### Laravel Telescope Alternatives: Building a Lightweight Request Inspector with Middleware

Telescope is powerful but heavy for production. Learn how to build a focused, low-overhead request inspector u...

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

 16 Jun 2026     1 min read  

  Read    

 ](https://www.msaied.com/articles/laravel-telescope-alternatives-building-a-lightweight-request-inspector-with-middleware) [ ![RAG Pipelines in Laravel: Chunking, Embedding, and Retrieval with pgvector](https://cdn.msaied.com/215/e037e13535aa77822f879ee829ec3f68.png) laravel ai pgvector 

### RAG Pipelines in Laravel: Chunking, Embedding, and Retrieval with pgvector

Build a production-ready Retrieval-Augmented Generation pipeline in Laravel using pgvector, OpenAI embeddings,...

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

 16 Jun 2026     3 min read  

  Read    

 ](https://www.msaied.com/articles/rag-pipelines-in-laravel-chunking-embedding-and-retrieval-with-pgvector) [ ![Laravel Pest: Architecture Tests, Mutation Testing, and Type Coverage in CI](https://cdn.msaied.com/214/0d4822fa8ee1765d0689e387dd849d92.png) laravel pest testing 

### Laravel Pest: Architecture Tests, Mutation Testing, and Type Coverage in CI

Go beyond feature tests. Learn how to enforce architectural rules, catch logic gaps with mutation testing, and...

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

 16 Jun 2026     4 min read  

  Read    

 ](https://www.msaied.com/articles/laravel-pest-architecture-tests-mutation-testing-and-type-coverage-in-ci) 

   [  ![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)
