rainlab/livewire-plugin
Integrates Livewire with October CMS and RainLab plugins, letting you build reactive, dynamic components without full page reloads. Provides the glue for mounting Livewire components inside RainLab-powered pages and themes—a lightweight bridge for existing October CMS projects.
Install. Install both Livewire and the October plugin — the plugin ships the Twig bridge, not Livewire itself:
composer require livewire/livewire
php artisan plugin:install RainLab.Livewire
Or from the backend: Settings → Updates & Plugins → Install and search for Livewire.
Look here first, in this order:
plugins/rainlab/livewire/Plugin.php — shows what gets registered on boot (Twig extension, view paths, Livewire component namespaces).plugins/rainlab/livewire/classes/ or twig/) — this is the authoritative source for the exact tag/function signature. Everything below assumes the canonical forms; verify against this file before you write 30 templates.plugins/rainlab/livewire/README.md — short, but confirms which Livewire major version the plugin was tested against. Pin accordingly.First use case — a counter in a CMS page. Register a component in your own plugin's Plugin.php boot (October's autoloader does not map app/Http/Livewire, so you must register explicitly):
use Livewire\Livewire;
public function boot()
{
Livewire::component('counter', \Acme\Site\Components\Counter::class);
}
Then drop it into a CMS page or partial:
{% livewire 'counter' %}
Add the runtime assets once in your theme layout — {% livewireStyles %} before </head>, {% livewireScripts %} before </body>. Getting these in the layout (not in a partial) is the difference between a working component and a mysterious "Livewire is not defined" in the console.
If nothing renders and no error appears, the tag is not registered — the plugin was installed but not enabled, or composer update replaced it. Check php artisan plugin:list.
Passing server-side data into a component. Treat the tag's second argument as a props array sourced from the Twig context; this is the cleanest way to bridge CMS page variables into the component:
{% set featured = record.posts|slice(0, 3) %}
{% livewire 'post-list' with { ids: featured|map(p => p.id), heading: 'Latest' } %}
Keep props small and serializable. Livewire snapshots props into the DOM on every render — passing an entire October model or a Collection bloats the payload and breaks on hydration. Pass IDs, re-query inside mount().
Component registration belongs in your own plugin, not the theme. Livewire resolves class names at runtime; theme-level code/ snippets cannot be autoloaded. The consistent pattern:
plugins/acme/site/components/plugins/acme/site/components/*.htm (or .blade.php) with a registered view namespace: $this->registerViewPath() / View::addNamespace('acme.site', plugins_path('acme/site/components')), then return view('acme.site::counter');Livewire::component() call per class, ideally driven by a loop over a manifest array so it stays tidy as the count grows.Loops and repeated components. Livewire needs a stable key, otherwise DOM morphing swaps state between rows:
{% for product in products %}
{% livewire 'add-to-cart' with { id: product.id } key: product.id %}
{% endfor %}
key may be expressed via the tag or via the component's :key on <livewire:...>; check the extension's supported arguments.
Keep October AJAX and Livewire in separate islands. Both frameworks own the page's JS lifecycle. Do not nest data-request handlers inside a Livewire component's wrapper, and do not put a Livewire component inside a partial that October re-renders via {% ajaxPartial %} or data-request-update. The practical split:
Forms. A Livewire form component is a much better fit than data-request for anything with validation feedback, dirty state, or file uploads. Use wire:model.live sparingly (it round-trips on every keystroke); prefer wire:model.blur. October's {% flash %} messaging won't appear inside a Livewire component — return a public $status property and render it in the component's Blade view instead.
Pagination. Livewire's paginator views are Blade files under resources/views/vendor/livewire; publish with php artisan livewire:publish --pagination and restyle to match your theme. If your theme relies on October's pagination partial, expect markup divergence and a stray Tailwind/Bootstrap mismatch.
Navigation. wire:navigate will replace the <body>, which kills any theme JS initialised on DOMContentLoaded (sliders, mobile menus, October's own {% framework %} bindings). If you enable it, re-init on the livewire:navigated event:
document.addEventListener('livewire:navigated', () => window.initThemeWidgets?.());
For most October sites, keeping full page loads and using Livewire only for embedded widgets is the lower-risk default.
Route placement matters. Livewire registers /livewire/update (older versions: /livewire/message). October's CMS page router is broad; if those routes are matched by a wildcard page, you get HTML back where JSON is expected. Confirm the route exists and wins:
php artisan route:list | grep livewire
If it 404s in a theme that uses a catch-all {{ slug }} page, move the Livewire route registration earlier or exclude the prefix in your routing config.
Assets and Alpine, the #1 source of weirdness. {% livewireScripts %} bundles Alpine. If your theme already ships Alpine (increasingly common with Tailwind-based October themes), you get "Alpine has already been loaded" or duplicated reactivity. Resolve by removing your Alpine include, or by calling Livewire::useScriptTagAttributes(['data-navigate-once' => true])-style config in a service provider. Also ensure {% livewireScripts %} comes after {% framework extras %} so it can attach its own listeners last.
Don't cache around Livewire. October's {% cache %} tag and the {% put %}/{% placeholder %} caching will store the rendered HTML including the Livewire snapshot. Users then interact with a stale component fingerprint and get 419/500 on the first request, or silently duplicated DOM. Keep {% livewire %} tags outside every caching block.
CSRF and the token. Livewire reads the token from a meta tag (<meta name="csrf-token">) or the XSRF-TOKEN cookie. October themes often skip the meta tag because data-request uses a different mechanism. If you see 419 Page Expired on wire:click, add the meta tag to your layout head. This is the single most common first-day bug.
Theme context is absent in the AJAX request. Livewire's request cycle does not bootstrap the active theme the same way a CMS page render does. Inside a component's render() or Blade view, asset() works, but Twig-style helpers, Cms::page(), and theme-specific globals may return null. Do not call $this->page or rely on the active theme's variables in a component — resolve data through models or explicitly passed props.
Debugging method. Open the network tab and inspect the POST /livewire/update request before touching any PHP:
| Symptom | Likely cause |
|---|---|
| 419 | Missing CSRF meta tag |
| 404 | Livewire route shadowed / not registered |
| 500 with "component not found" | Livewire::component() never ran, or the plugin booted before your plugin |
| 200 but nothing changes | Property not public, or missing wire:model — the snapshot is fine but no re-render was triggered |
| 200 but wrong HTML | A Blade view resolving to the wrong namespace; log the path with dd(view()->getFinder()->find(...)) |
Also set APP_DEBUG=true locally and enable Livewire::listen / the livewire.php config's inject_assets false if you want to manage assets manually — publishing the config with php artisan livewire:publish --config is worth doing early so you're not editing vendor files later.
Version pinning is not optional. Twenty-seven stars and zero dependents means this package is not battle-tested by a crowd — you are the test suite. Pin both rainlab/livewire-plugin and livewire/livewire to exact minor versions in composer.json, and re-test after any October core upgrade. The license is NOASSERTION on Packagist; confirm the actual license file in the repo before shipping it to a client, since "no asserted license" is a legal conversation, not a code problem.
Extension points if the tag falls short. You can bypass the Twig tag entirely and render Livewire from Twig by pre-rendering in a CMS page's onStart() and outputting raw HTML:
function onStart()
{
$this['widget'] = \Livewire\Livewire::mount('counter', ['start' => 3]);
}
then {{ widget|raw }}. This is the escape hatch when you need conditional component selection or need to feed in data the tag can't express. It costs you wire:key handling, so use it deliberately rather than as the default.
Where to extend the plugin itself. If you need a new Twig argument or a {% livewireScripts %} variant (defer, nonce for CSP), extend the plugin's Twig extension class from your own plugin rather than forking — Event::listen('cms.page.beforeRenderPage') and View::share are usually enough for asset injection. Fork only if the tag's parsing itself is wrong.
One more: the plugin renders components in embedded mode. October owns routing, so Livewire full-page components (Route::get('/x', Component::class)) are the wrong tool here — they'd fight the CMS router. If you want a "page" that is really a Livewire component, make it a CMS page whose entire body is a single {% livewire %} tag and keep the theme layout.
How can I help you explore Laravel packages today?