tonysm/stimulus-laravel
Stimulus Laravel brings Stimulus.js to your Laravel apps with zero-config controller registration, Blade helpers, and seamless Vite integration. Drop controllers into a folder and they auto-load, adding lightweight interactivity to server-rendered views without a full SPA.
hotwired-laravel/stimulus-laravel is the official Hotwire-flavoured scaffolding package for Stimulus in a Laravel app: Artisan commands that generate controllers, publish config, and wire the controller registry into your Vite entrypoint. It is a build/scaffolding tool, not a runtime library — nothing ships to the browser from the package itself.
Release 1.5.0 only widens the framework constraint to include Laravel 13. There are no code, config, or asset changes, so it is a drop-in bump.
Minimal path from zero:
composer require --dev hotwired-laravel/stimulus-laravel (add it as a dev dependency — it is only needed while scaffolding).npm install @hotwired/stimulus if the installer does not do it for you.php artisan stimulus:install — publishes config/stimulus.php, creates resources/js/controllers/, and adds the import/registration to your JS entrypoint (resources/js/app.js by default).php artisan stimulus:make hello — generates resources/js/controllers/hello_controller.js.npm run dev, then in Blade:<div data-controller="hello">
<button data-action="click->hello#greet">Greet</button>
<span data-hello-target="output"></span>
</div>
First real use case: a small interactive island on an otherwise server-rendered page — a dropdown toggle, a character counter on a form textarea, or a "copy to clipboard" button. Reach for it before pulling in a full SPA.
Where to look first: the generated hello_controller.js (shows the class/export shape the loader expects), the registration snippet in resources/js/app.js, and config/stimulus.php for the controllers path.
Naming is the contract. File name determines the HTML identifier. hello_controller.js → data-controller="hello". Nested directories map to --: controllers/admin/user_form_controller.js → data-controller="admin--user-form". Underscores in filenames become dashes in the DOM; kebab-case in Blade, snake_case on disk.
Generate, don't hand-roll. php artisan stimulus:make user_form scaffolds the correct suffix and placement so the registry picks it up without touching the index file. Run npm run dev alongside it and Vite HMR picks up new controllers immediately.
Bootstrapping from Blade server data. Pass Laravel state in data-* attributes rather than embedding URLs and CSRF tokens in JS bundles:
<div data-controller="search"
data-search-url-value="{{ route('api.search') }}"
data-search-token-value="{{ csrf_token() }}">
Read them via Stimulus Values (this.urlValue, this.tokenValue) so they are typed, defaulted, and observable via xxxValueChanged().
Keep controllers thin. Controllers should own DOM wiring only — targets, actions, values — and delegate real logic to plain ES modules under resources/js/lib/. Those modules are unit-testable without a DOM and Stimulus controllers stay short.
Turbo coexistence. If you also use Turbo (same Hotwire family), controllers reconnect on every navigation: put setup in connect() and teardown in disconnect(), and never rely on DOMContentLoaded inside a controller. Elements preserved across visits keep their instance; replaced elements get a fresh one.
Third-party controllers. Community Stimulus controllers from npm are registered through the package's controller registry rather than by editing the generated index by hand — check the README section on importing npm-packaged controllers, since that registration file is what stimulus:install generates and what stimulus:make expects to find.
Testing. Browser-level assertions (assertAttribute('data-controller', ...) in Dusk, or a Playwright smoke test) catch the common failure where a controller exists but the registry never loaded it. Keep CI building assets (npm ci && npm run build) before any test that touches a page using a controller.
data-controller does nothing, the registry import is missing from your entrypoint, or the entrypoint is not the script tag in your Blade layout. Check resources/js/app.js first, then the built asset.composer install --no-dev on the server, stimulus:make is unavailable — fine, because controllers should be generated and committed locally, not at deploy time. Do not put generation in a deploy hook.illuminate/* range to admit Laravel 13. It is safe on Laravel 10–12 as well; no config republish, no migrations, no asset rebuild required. composer update hotwired-laravel/stimulus-laravel is the whole upgrade.config/stimulus.php (controllers path, registry location), run php artisan config:clear locally — a stale cached config silently generates controllers where the loader does not look.data-controller in Blade produces a Stimulus "Missing controller" console error at best, a dead component at worst. Grep the identifier before renaming.data-* attributes and coexist, but do not put data-controller and x-data on the same element managing the same state — pick one owner per component.connect() first; if it never fires, it is a registration problem, not a controller-logic problem. If it fires but targets are empty, the target attribute name or the static targets array is wrong — target names must match exactly, including case in the static array.How can I help you explore Laravel packages today?