PHPackages                             mathiasgrimm/laravel-puff - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. mathiasgrimm/laravel-puff

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

mathiasgrimm/laravel-puff
=========================

On-demand warming for scale-to-zero Laravel apps - no cold start when it counts. A tiny activity-driven warmer that wakes the stack just before the user's request.

v1.3.0(1mo ago)4655↑50%MITPHPPHP ^8.2CI passing

Since Jun 29Pushed 3w agoCompare

[ Source](https://github.com/mathiasgrimm/laravel-puff)[ Packagist](https://packagist.org/packages/mathiasgrimm/laravel-puff)[ RSS](/packages/mathiasgrimm-laravel-puff/feed)WikiDiscussions main Synced 2w ago

READMEChangelog (5)Dependencies (10)Versions (6)Used By (0)

 [![Laravel Puff](https://raw.githubusercontent.com/mathiasgrimm/laravel-puff/main/art/banner.png)](https://raw.githubusercontent.com/mathiasgrimm/laravel-puff/main/art/banner.png)

Laravel Puff
============

[](#laravel-puff)

> On-demand warming for scale-to-zero Laravel apps. No cold start when it counts.

 [![Latest Version on Packagist](https://camo.githubusercontent.com/a5afe81e86c1829b98a464b1eda168c1a4dc46d1ab9139ab9d3ec7c3bb8394a1/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f6d6174686961736772696d6d2f6c61726176656c2d707566662e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/mathiasgrimm/laravel-puff) [![Tests](https://camo.githubusercontent.com/249064d4d27cdd8541b8f63b58da5edb127c68be12c69a02467556f41c7bf79c/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f6d6174686961736772696d6d2f6c61726176656c2d707566662f74657374732e796d6c3f6272616e63683d6d61696e266c6162656c3d7465737473267374796c653d666c61742d737175617265)](https://github.com/mathiasgrimm/laravel-puff/actions/workflows/tests.yml) [![Total Downloads](https://camo.githubusercontent.com/2fc0037b852ce28bebb9e51375700f75dc128b5612e2aa393f67cc08791bd59d/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f6d6174686961736772696d6d2f6c61726176656c2d707566662e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/mathiasgrimm/laravel-puff) [![License](https://camo.githubusercontent.com/ec7a86dce02cf618d5628c519d59b574fb18a4cfb53cb8b5894a0ddb9a67dc71/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f6c2f6d6174686961736772696d6d2f6c61726176656c2d707566662e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/mathiasgrimm/laravel-puff)

Note

This is an independent, community package. It is not an official or first-party Laravel package, and is not affiliated with, endorsed by, or sponsored by Laravel or Laravel Cloud. "Laravel" is a trademark of its respective owner.

Laravel Cloud's scale-to-zero is spectacular: it parks your environment when idle and wakes it in ~500ms. **Laravel Puff** makes it even better. When a visitor shows *intent* to act (moving the mouse, typing, scrolling, or returning to the tab), the browser fires a lightweight, throttled `POST /puff` that wakes the environment itself and its related resources, your database and Redis, *ahead of* the real request, so the cold start is already paid for by the time they click.

It is **not** a poller. A heartbeat that pings on a timer would keep the environment awake forever and defeat scale-to-zero. Laravel Puff only fires on genuine human activity, so an idle app still sleeps (and stops billing) the moment everyone leaves.

Features
--------

[](#features)

- **Activity-driven, not timed.** Warms on real intent (mouse, keyboard, scroll, touch, tab focus), so idle apps still scale to zero.
- **Tiny, framework-agnostic core.** No axios, no Wayfinder, no Inertia coupling.
- **Throttled and public by default.** One request per 60s, runs everywhere out of the box.
- **Vue and React adapters included.** Svelte and Livewire/Blade are additive.
- **Zero-config CSRF.** Reads Laravel's `XSRF-TOKEN` cookie automatically.

Requirements
------------

[](#requirements)

- PHP 8.2+
- Laravel 11, 12, or 13

Installation
------------

[](#installation)

```
composer require mathiasgrimm/laravel-puff
php artisan puff:install
```

`puff:install` does three things:

1. Publishes the config and the warm-up JS (the framework-agnostic core + a framework adapter) into `resources/js/laravel-puff/`.
2. Wires a global `startPuff()` call into your JS entry (`resources/js/app.ts`or `resources/js/app.tsx`), so warming runs on every page out of the box.
3. Adds `puff:publish` to your `composer.json` `post-update-cmd` (a one-line string edit that leaves the rest of the file untouched) so the stub re-syncs with the installed package version on every `composer update`. If you have no `post-update-cmd` yet, it prints the line to add instead of reformatting.

The stack is auto-detected (Vue or React) from your entry file and `package.json`, so the Vue and React starter kits both work with a bare `php artisan puff:install`. If it can't tell, the command stops and asks you to pass `--stack=vue` or `--stack=react` rather than guessing.

Flags: `--no-wire` to skip step 2, `--no-scripts` to skip step 3, `--entry=path/to/app.tsx` to target a different entry file, `--force` to overwrite published files, `--stack=vue|react` to override auto-detection.

That's it. Move the mouse or switch back to the tab and you'll see a single `POST /puff` → `204`, throttled to at most one per 60 seconds.

### Keeping the stub up to date

[](#keeping-the-stub-up-to-date)

The published JS (`resources/js/laravel-puff/`) is a copy, so `composer update`alone won't refresh it. Treat that folder as package-owned (don't edit it). `puff:install` already adds the following to your `composer.json`, so every update re-publishes the stub from the installed version:

```
"scripts": {
    "post-update-cmd": [
        "@php artisan puff:publish --ansi"
    ]
}
```

If you installed with `--no-scripts`, add it yourself. `puff:publish`force-republishes the core + the adapter for whichever stack you installed (it never touches `config/puff.php`, which stays yours). You can also run it by hand any time: `php artisan puff:publish`.

Usage
-----

[](#usage)

### Wiring it yourself

[](#wiring-it-yourself)

`startPuff()` is the framework-agnostic core (no Vue required). If you skipped `--no-wire`, add it to your entry manually:

```
import { startPuff } from '@/laravel-puff/puff';

startPuff();
```

### Vue composable (opt-in)

[](#vue-composable-opt-in)

Prefer per-component control with automatic cleanup on unmount? Use the Vue composable in a layout's `` instead:

```
import { usePuff } from '@/laravel-puff/usePuff';

usePuff();
```

To restrict warming (e.g. authenticated users only), pass an `isEnabled`predicate:

```
import { usePage } from '@inertiajs/vue3';

usePuff({ isEnabled: () => !!usePage().props.auth?.user });
```

### React hook (opt-in)

[](#react-hook-opt-in)

On React (e.g. the React starter kit), the published `usePuff` is a hook that starts warming on mount and cleans up on unmount. Call it once in a layout:

```
import { usePuff } from '@/laravel-puff/usePuff';

usePuff();
```

To restrict warming (e.g. authenticated users only), pass an `isEnabled`predicate:

```
import { usePage } from '@inertiajs/react';

usePuff({ isEnabled: () => !!usePage().props.auth?.user });
```

Configuration
-------------

[](#configuration)

`config/puff.php`:

```
return [
    'register_route' => true,          // set false to define the route yourself
    'path'           => 'puff',        // POST /puff
    'name'           => 'puff',        // route name
    'middleware'     => ['web'],       // public by default; add 'auth' to restrict
    'throttle'       => '60,1',         // rate limit (maxAttempts,decayMinutes); null to disable
    'warm' => [
        'database' => [
            'enabled'     => true,
            'connections' => [],       // empty = default; or ['mysql', 'reports']
        ],
        'redis' => [
            'enabled'     => true,
            'connections' => [],       // empty = default; or ['default', 'cache']
        ],
    ],
];
```

CSRF works out of the box: the core reads Laravel's `XSRF-TOKEN` cookie and sends it as the `X-XSRF-TOKEN` header, so no meta tag or extra setup is needed.

### Frontend options

[](#frontend-options)

`usePuff(options)` / `startPuff(options)` accept:

OptionDefaultDescription`url``'/puff'`Endpoint to POST to`intervalSeconds``60`Minimum gap between requests, also measured from page load`events``['mousemove','keydown','scroll','touchstart']`Activity events (on `window`) that trigger a warm`method``'POST'`HTTP method`warmOnVisible``true`Also warm when the user returns to the tab (`visibilitychange`)`isEnabled`always-onReturn `false` to skip (e.g. for guests)The framework-agnostic core (`resources/js/laravel-puff/puff.ts`) exports `startPuff(options): () => void` and returns a `stop()` cleanup, if you want to wire it up yourself in another framework.

### When the first puff fires

[](#when-the-first-puff-fires)

Puff does **not** warm on page load. The page you just loaded was served by the app, so the stack is already warm, and firing then would be wasted. The throttle is seeded from load time, so the earliest a puff can go out is one `intervalSeconds`(60s by default) later, and only on genuine activity (mouse, keyboard, scroll, touch, or returning to the tab). That is by design: it covers the window where an idle stack may have scaled to zero, without spending a request while it is still warm.

### Tuning `intervalSeconds` to your scale-to-zero setting

[](#tuning-intervalseconds-to-your-scale-to-zero-setting)

The best value mirrors how long your environment stays up while idle. If Laravel Cloud is configured to sleep after 5 minutes of inactivity, a puff only needs to land inside that window to keep the stack warm for an active user, so an `intervalSeconds` around that idle timeout (e.g. `300` for 5 minutes) is a good fit: it warms often enough to stay ahead of a sleep, while keeping request volume to a minimum. Setting it much shorter than your idle timeout just adds requests without buying earlier coverage; setting it much longer risks the stack napping between puffs. When in doubt, match it to your scale-to-zero idle setting.

Testing
-------

[](#testing)

```
composer test
```

Contributing
------------

[](#contributing)

Contributions are welcome. Please open an issue to discuss substantial changes before sending a pull request, and make sure `composer test` and `composer analyse` pass.

Security
--------

[](#security)

If you discover a security vulnerability, please email  instead of using the issue tracker.

Credits
-------

[](#credits)

- [Mathias Grimm](https://github.com/mathiasgrimm)
- [All Contributors](https://github.com/mathiasgrimm/laravel-puff/contributors)

License
-------

[](#license)

MIT. See [LICENSE.md](LICENSE.md).

###  Health Score

46

—

FairBetter than 92% of packages

Maintenance93

Actively maintained with recent releases

Popularity23

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity50

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~0 days

Total

5

Last Release

44d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/56f4dbd80bb6510bdc2adc49fcf7ec925cb45535b3bfdadf0964a09d678cf8ab?d=identicon)[mathiasgrimm](/maintainers/mathiasgrimm)

---

Top Contributors

[![mathiasgrimm](https://avatars.githubusercontent.com/u/450069?v=4)](https://github.com/mathiasgrimm "mathiasgrimm (50 commits)")

---

Tags

laravelwarmupserverlesslaravel cloudscale-to-zerocold-start

###  Code Quality

TestsPest

Static AnalysisPHPStan

Code StyleLaravel Pint

### Embed Badge

![Health badge](/badges/mathiasgrimm-laravel-puff/health.svg)

```
[![Health](https://phpackages.com/badges/mathiasgrimm-laravel-puff/health.svg)](https://phpackages.com/packages/mathiasgrimm-laravel-puff)
```

###  Alternatives

[markwalet/nova-modal-response

A Laravel Nova asset for Modal responses on an action.

17930.8k](/packages/markwalet-nova-modal-response)[api-platform/laravel

API Platform support for Laravel

58190.1k21](/packages/api-platform-laravel)[ronasit/laravel-helpers

Provided helpers function and some helper class.

2087.0k31](/packages/ronasit-laravel-helpers)[team-nifty-gmbh/tall-datatables

Server-side rendered datatables for Laravel and Livewire

1422.0k5](/packages/team-nifty-gmbh-tall-datatables)[crumbls/layup

A visual page builder plugin for Filament 5 — Divi-style grid layouts with extensible widgets.

604.0k2](/packages/crumbls-layup)[tomshaw/electricgrid

A feature-rich Livewire package designed for projects that require dynamic, interactive data tables.

119.8k](/packages/tomshaw-electricgrid)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
