Upgrade
Laravel Livewire Documentation Reference
Since Magewire is heavily inspired by Laravel Livewire, many concepts are either identical or very similar. To avoid duplicating documentation, this page only covers Magewire-specific and platform-specific details. For all general concepts and in-depth explanations, you can refer to the corresponding Laravel Livewire documentation.
This page covers upgrading from Magewire V1 to V3. Read it top-to-bottom the first time, then use it as a reference. Copy the checklist at the end into a ticket or PR description when you start the work.
TL;DR
- Upgrade PHP, Magento, and Composer requirements.
- Install V3:
composer require magewirephp/magewire:^3.0. - Install or update the appropriate theme compatibility package so Alpine is loaded once.
- Add
#[HandleBackwardsCompatibility]to legacy components, then test each interaction. The layer covers specific V1 browser behaviors, but it is not a guarantee that unchanged component code will work. - Migrate one component at a time using the checklist below.
- Drop the BC attribute per component once it is fully V3-native.
- Remove obsolete BC attributes and any companion package installed only for legacy compatibility.
The BC layer is the key insight: V3 does not force you to migrate on day one. You can upgrade, keep shipping, and migrate at your own pace.
Versioning
Magewire V2 never existed. Magewire V1 was built on Livewire V2, which created persistent version confusion: a "Magewire 1" that was effectively at Livewire's v2 feature line. V3 aligns Magewire's major version with Livewire's, so V2 was skipped entirely. Going forward, Magewire's major version tracks Livewire's.
See Versioning for the full versioning scheme, including how subpackages are tagged.
Requirements
Before running composer update, bring the surrounding environment up to spec.
- Magento or Mage-OS on a release line supported by the selected Magewire tag. Magewire 3.6's tested floor is Magento Open Source 2.4.6-p15 or Mage-OS 1.3.1.
- PHP
8.2or later. - Composer 2.
- A compatible theme integration. Magewire bundles Alpine, while the compatibility package coordinates it with the theme's own loader.
Do not remove theme assets globally
A double-Alpine page fails in confusing ways, but removing a theme's loader globally can break pages without Magewire. For Hyvä, install magewirephp/magewire-hyva-theme and let it select the correct loader per page.
Install the new version
composer require magewirephp/magewire:^3.0
bin/magento setup:upgrade
bin/magento setup:di:compile # production mode only
bin/magento setup:static-content:deploy # production mode only
bin/magento cache:flush
If you use the admin integration (new in V3), also install:
composer require magewirephp/magewire-admin
bin/magento module:enable Magewirephp_MagewireAdmin
bin/magento setup:upgrade
bin/magento cache:flush
In developer mode, skip the di:compile and static-content:deploy steps; Magento picks up the changes on the next request.
Enable the backwards-compatibility layer
Magewire V3 ships a BC layer for specific V1 browser behaviors while components are migrated. It does not guarantee
that an unchanged V1 component will work. Turn it on per component with the
#[HandleBackwardsCompatibility] attribute, then test the complete interaction:
use Magewirephp\Magewire\Features\SupportMagewireBackwardsCompatibility\HandleBackwardsCompatibility;
#[HandleBackwardsCompatibility]
class LegacyCart extends \Magewirephp\Magewire\Component
{
// The documented BC transformations are enabled for this component.
}
// Explicitly opt out: useful to override a theme-level auto-enable rule:
#[HandleBackwardsCompatibility(enabled: false)]
class ModernCart extends \Magewirephp\Magewire\Component { /* … */ }
The attribute lives in Magewirephp\Magewire\Features\SupportMagewireBackwardsCompatibility, not under Attributes\. Getting the namespace right matters; a wrong use statement silently leaves the component on V3 defaults.
What the BC layer does automatically
When BC is enabled for a component, at runtime Magewire:
- Rewrites
wire:model→wire:model.live,wire:model.defer→wire:model,wire:model.lazy→wire:model.blur,wire:model.delay.Xms→wire:model.live.debounce.Xms. - Returns a live-by-default proxy from
$wire.entangle('…'). - Re-triggers deprecated JS hook names (
component.initialized,element.updating,message.sent, …) alongside their V3 replacements. - Proxies
component.data→component.$wireandcomponent.deferredActions→component.queuedUpdates. - Adapts the selected V1
updating*andupdated*hook argument shapes covered by its registered resolvers. - Keeps
$this->id,$this->getPublicProperties(), and the V1emit*()family available on the baseComponentclass via theHandlesComponentBackwardsCompatibilitytrait.
What the BC layer does not do
You still have to manually handle:
- Custom lifecycle signatures that are not covered by the registered BC argument resolvers.
- Public property type mismatches.
- Validation rule format changes.
- Behavioural changes in Magento or Hyvä that Magewire wraps.
- Any custom JS that listens directly on internal APIs that moved.
BC buys time; it does not eliminate migration work.
The memo.bc.enabled flag
BC pivots on a single flag in the snapshot memo: memo.bc.enabled. When present and truthy, the browser shims
activate for that component's DOM subtree. The support Feature and deprecated PHP helpers remain registered on the
base component, so the flag should be understood as a browser-behavior switch rather than complete removal of all BC code.
Resolution priority for the flag:
#[HandleBackwardsCompatibility]attribute (eitherenabled: trueorenabled: false): wins in all cases.- Programmatic assignment:
store($component)->set('magewire:bc', true)from a Component Hook or Feature. - Theme default. A companion package may attempt a default, but current Hyvä Checkout migrations should add the attribute explicitly; see Theming → Hyvä Checkout BC.
Breaking changes
The sections below cover the key source-verified changes. Custom integrations may depend on additional internal behavior, so test the complete interaction rather than treating this as an exhaustive compatibility contract.
wire:model is deferred by default
| V1 | V3 | BC auto-handled? |
|---|---|---|
wire:model |
wire:model.live |
Yes |
wire:model.defer |
wire:model |
Yes |
wire:model.lazy |
wire:model.blur |
Yes |
wire:model.delay.500ms |
wire:model.live.debounce.500ms |
Yes |
V1's default was "sync on every keystroke", V3's default is "sync on form submit / next request". If you want instant sync in a V3 component, you must opt in with .live. The .defer / .lazy / .delay modifiers no longer exist in V3.
Entangle is deferred by default
<!-- V1: live by default -->
<div x-data="{ open: $wire.entangle('open') }">…</div>
<!-- V3: deferred by default. Add .live for instant sync. -->
<div x-data="{ open: $wire.entangle('open').live }">…</div>
BC auto-restores the V1 live-by-default proxy. Once the component is migrated, decide per entangle call whether you want live or deferred; each call site may want a different answer.
Event listeners use the #[On] attribute
use Magewirephp\Magewire\Attributes\On;
// V1
protected $listeners = ['cart-updated' => 'refresh'];
public function refresh(): void { /* … */ }
// V3
#[On('cart-updated')]
public function refresh(): void { /* … */ }
$listeners still works (the base Component class still reads it) but is discouraged. #[On] gives better IDE support and lets a single method respond to multiple events via multiple attributes.
Dispatch from PHP with $this->dispatch('event-name', foo: 'bar'). This replaces V1's $this->emit('event-name', ['foo' => 'bar']). The BC trait keeps emit, emitUp, emitSelf, and emitTo available, but they are thin wrappers around dispatch() now.
Hook / event renames (JS)
| V1 name | V3 name | BC auto-handled? |
|---|---|---|
component.initialized |
component.init |
Yes |
element.initialized |
element.init |
Yes |
element.updating |
morph.updating |
Yes |
element.removed |
morph.removed |
Yes |
message.sent |
commit |
Yes |
message.failed |
commit → fail() |
Yes |
message.received |
commit → succeed() |
Yes |
message.processed |
commit → succeed() → queueMicrotask |
Yes |
The V3 commit hook is callback-based: the before half runs synchronously; the returned closure receives succeed / fail / respond continuations you can hook into. See Component Hooks for the full signature.
Component property aliases (JS)
| V1 | V3 | BC auto-handled? |
|---|---|---|
component.data |
component.$wire |
Yes |
component.deferredActions |
component.queuedUpdates |
Yes |
PHP component API
| V1 | V3 | Status |
|---|---|---|
$this->emit('e', [...]) |
$this->dispatch('e', ...) |
V1 call kept under the BC trait; prefer dispatch(). |
$this->emitUp('e', ...) |
$this->dispatch('e', ...) |
V3 events bubble by default; there is no up() modifier. |
$this->emitSelf('e', ...) |
$this->dispatch('e', ...)->self() |
V1 kept; prefer ->self() chain. |
$this->emitTo('other', 'e', …) |
$this->dispatch('e', ...)->to('other') |
V1 kept; prefer ->to() chain. |
$this->dispatchBrowserEvent('e', …) |
$this->dispatch('e', ...) and a browser event listener |
V1 helper remains deprecated on the base component. Component::js() is not active. |
$this->getPublicProperties() |
$this->all() |
V1 kept under BC trait. |
public $this->id |
$this->id() / $this->getId() |
The deprecated public property currently remains on every base component through the BC trait. |
Lifecycle hooks
V3 adds more lifecycle hooks than V1 exposed. All are optional:
| Hook | When it fires |
|---|---|
boot() |
Every request. On updates, public properties have already been restored, but hydrate() has not run yet. |
booted() |
Every request, after the mount or hydrate hook sequence. |
mount(...$namedArguments) |
Initial render only; receives named values from magewire:mount:*. |
hydrate() |
Every subsequent request, after public properties are restored. |
hydrateXxx() |
Hydrate for a specific property. |
updating($prop, $value) |
Before any property update. |
updatingXxx($value) |
Before a specific property updates. |
updated($prop, $value) |
After any property update. |
updatedXxx($value) |
After a specific property updates. |
rendering($view, $data) |
Before the template renders. |
rendered($view, $html) |
After the template renders. |
dehydrate() |
Before state is serialized back to snapshot. |
dehydrateXxx() |
Dehydrate for a specific property. |
exception(\Throwable $e, callable $stopPropagation) |
On exception. |
V1's hydrate() / dehydrate() signatures survive, while V3 adds per-property variants and booted().
The framework calls trait initialize* hooks internally, but a plain initialize() method on the component is not a
public lifecycle hook in Magewire 3.6.
updating / updated argument order
Generic V3 hooks receive the full property path followed by the new value:
updating($fullPath, $newValue) and updated($fullPath, $newValue). Property-specific hooks receive the new value
and, for nested data, an optional key. The BC plugin can transform arguments for a BC-enabled component. Check the
UpdatingUpdatedArgumentSwapResolver rule if a V1 hook receives unexpected values.
There is no render() method on Component
V1 examples occasionally showed a custom render(): string method to pick a template per state. V3 has no such method. The block's template renders automatically; to swap templates per state, use the rendering hook:
public function rendering(): void
{
$this->magewireBlock()->setTemplate(
$this->state === 'review'
? 'Vendor_Module::magewire/review.phtml'
: 'Vendor_Module::magewire/default.phtml'
);
}
Register service collections in the active area
Magewire's Feature, Mechanism, Synthesizer, and Resolver arrays are configured at Magento's area-specific DI stage.
An item added to the same array in global etc/di.xml can be replaced when Magento loads the later frontend or
adminhtml array. Register collection items in the area where Magewire uses them.
Do not move every Magewire-related <type> block indiscriminately. Normal global preferences, plugins, and constructor
arguments remain valid in etc/di.xml; move the collection additions shown below to etc/frontend/di.xml and/or
etc/adminhtml/di.xml.
Registration targets:
<!-- Features / custom Component Hooks -->
<type name="Magewirephp\Magewire\Features">
<arguments>
<argument name="items" xsi:type="array">
<item name="my_feature" xsi:type="array">
<item name="type" xsi:type="string">Vendor\Module\Magewire\Features\MyFeature</item>
<item name="sort_order" xsi:type="number">50000</item>
<item name="boot_mode" xsi:type="number">30</item>
</item>
</argument>
</arguments>
</type>
<!-- Synthesizers (HandleComponents mechanism) -->
<type name="Magewirephp\Magewire\Mechanisms\HandleComponents\HandleComponents">
<arguments>
<argument name="synthesizers" xsi:type="array">
<item name="money" xsi:type="string">Vendor\Module\Magewire\Synthesizers\MoneySynth</item>
</argument>
</arguments>
</type>
<!-- Component resolvers -->
<type name="Magewirephp\Magewire\Mechanisms\ResolveComponents\Management\ComponentResolverManager">
<arguments>
<argument name="resolvers" xsi:type="array">
<item name="my_resolver" xsi:type="object" sortOrder="90000">
Vendor\Module\Mechanisms\ResolveComponents\ComponentResolver\MyResolver
</item>
</argument>
</arguments>
</type>
Features are Component Hooks now
V1's "Feature" convention was informal. V3 Features extend Magewirephp\Magewire\ComponentHook and expose a provide() method that subscribes to lifecycle events:
use Magewirephp\Magewire\ComponentHook;
use Magewirephp\Magewire\Component;
use function Magewirephp\Magewire\on;
class SupportMyFeature extends ComponentHook
{
public function provide(): void
{
on('render', function (Component $component) {
return function (string $html) {
// After-render transformation.
return $html;
};
});
}
}
If you registered a V1 "feature" as a plugin or observer, consider whether it should become a real Component Hook because the middleware semantics are usually cleaner.
Synthesizers replace V1's hydrators
V1 had a HydratorInterface. V3 uses Synthesizers: classes that explain how to serialise and deserialise a given type across the snapshot boundary.
Magewire ships synthesizers for scalars, arrays, \stdClass, and backed enums. A
\Magento\Framework\DataObject synthesizer is registered too, but its Magewire 3.6 array-cast implementation does
not guarantee a correct round trip for normal DataObject state. Keep that state in a public array until the
implementation is corrected. For custom value objects, write a Synth and register it:
class MoneySynth extends \Magewirephp\Magewire\Mechanisms\HandleComponents\Synthesizers\Synth
{
public static $key = 'mny';
public static function match($target): bool
{
return $target instanceof \Vendor\Module\Model\Money;
}
public function dehydrate(Money $target, $dehydrateChild): array
{
return [['amount' => $target->amount(), 'currency' => $target->currency()], []];
}
public function hydrate($value, $meta, $hydrateChild): Money
{
return new Money($value['amount'], $value['currency']);
}
}
Old HydratorInterface implementations survive under lib/MagewireBc/Model/HydratorInterface.php but are wrapped; convert to Synthesizers when you can.
Alpine.js is bundled and CSP
V3 ships the CSP build of Alpine inside its JavaScript bundle. Two consequences:
- Do not start a second Alpine instance on a Magewire page. Use the maintained theme compatibility package to coordinate loaders instead of removing a theme's Alpine script globally.
- CSP-mode Alpine does not evaluate JavaScript expressions with
eval/new Function. Arrow functions, template literals, destructuring, spread, and nested assignments inside Alpine directive expressions (x-on:click="…",x-init="…", etc.) are unavailable. Move complex logic intoAlpine.data()registrations or a utility onwindow.MagewireUtilities.
Plain <script> tags in your PHTML still use normal JS; only the expressions that Alpine itself evaluates are affected.
CSP fragments replace hand-rolled nonces
V1 templates sometimes carried hand-rolled CSP nonces or hashes on inline <script> tags. V3 offers Fragments: wrap any inline <script> in a fragment and Magewire injects the right nonce or hash automatically:
<?php
$fragment = $block->getData('view_model')->utils()->fragment();
$script = $fragment->make()->script()->start();
?>
<script>console.log('Hello');</script>
<?php $script->end(); ?>
Strip your hand-rolled nonces when you migrate the template. See Fragments.
Namespace changes
| V1 | V3 |
|---|---|
Magewirephp\Magewire\Attributes\HandleBackwardsCompatibility (never existed, common mis-import) |
Magewirephp\Magewire\Features\SupportMagewireBackwardsCompatibility\HandleBackwardsCompatibility |
Livewire\Mechanisms\HandleComponents\Synthesizers\Synth |
Magewirephp\Magewire\Mechanisms\HandleComponents\Synthesizers\Synth |
The On attribute namespace did not change: Magewirephp\Magewire\Attributes\On.
Migration workflow
The sequence below is recommended for a module with more than a handful of V1 components. Treat each numbered step as a separate PR or commit. They are cleanest in isolation.
1. Install V3 with BC on everything
Add #[HandleBackwardsCompatibility] to every V1 component in the module. Run the test suite; visit the site. The goal here is not to change behaviour; it is to prove that BC alone keeps the site green.
2. Migrate wire directives
Find every wire:model* in templates and rewrite against the table above. Remove .defer, .lazy, .delay. Add .live where you need instant sync, .blur where you need on-blur, .live.debounce.Xms for debounced live.
3. Migrate entangle calls
Grep for $wire.entangle( and decide per call site whether you want .live (instant sync) or deferred (the V3 default).
4. Migrate $listeners arrays to #[On]
Each entry becomes an #[On('event-name')] attribute on the target method.
5. Migrate JS hook names
If your theme or custom JS listens on message.sent, element.updating, component.initialized, or friends, move those listeners to the V3 names (see the rename table above). The BC layer re-triggers the old names for BC-enabled components, but removing your dependency on the old names is the cleaner end state.
6. Migrate PHP component calls
$this->emit(…)→$this->dispatch(…).$this->emitUp(…)→$this->dispatch(…)because V3 events bubble by default.$this->emitTo('other', …)→$this->dispatch(…)->to('other').$this->getPublicProperties()→$this->all().$this->id→$this->id()or$this->getId().
7. Move DI registrations
Move registrations that extend the Magewirephp\Magewire\Features or
Magewirephp\Magewire\Mechanisms item collections into etc/frontend/di.xml and, when needed,
etc/adminhtml/di.xml. Normal preferences, plugins, and constructor configuration may still belong in global
etc/di.xml; only move configuration that is explicitly area-scoped.
8. Drop the BC attribute per component
As each component becomes fully V3-native, switch its attribute to
#[HandleBackwardsCompatibility(enabled: false)] to disable the browser transformations. When every component in
the module is V3-native, remove the attribute entirely.
9. Remove obsolete BC integration
When every component on the site is migrated, remove the BC attributes. Do not edit Feature registration inside vendor packages. If a companion package was installed solely to support legacy components and is no longer required, remove that package through Composer and verify the merged layout before deployment.
Hyvä Checkout
Hyvä Checkout V1 is wired for Livewire V2 semantics. Install magewirephp/magewire-hyva-checkout, add
#[HandleBackwardsCompatibility] explicitly to legacy components, and verify the checkout end to end. The companion
package contains a container-based fallback, but the Magewire 3.6 resolver writes a default false value before that
fallback checks for an unset value. Implicit opt-in is therefore unreliable.
See Theming → Hyvä Checkout BC for the full detail.
Admin (new in V3)
Magewire V1 was storefront-only. V3 supports admin components through the companion package magewirephp/magewire-admin. If the site has admin Magewire use cases such as reactive grids, inline editors, or wizards, install it alongside core. See Admin → Installation and Admin → How it works.
Admin components use the exact same layout-XML / PHTML / $wire conventions as storefront. The argument name in layout XML stays magewire (same as storefront). The LayoutAdminResolver picks up admin-area blocks automatically; you never reference layout_admin in your own XML.
Common upgrade gotchas
The following issues have appeared during real upgrades. Check this list before opening an issue.
- "
wire:clickstopped working after upgrade": you probably still have a second Alpine loaded. Check the theme bundle and any layout XML that adds Alpine's script. - "Half my snapshots fail checksum validation": the Magento crypt key changed between the snapshot being issued and the request arriving. Flush FPC; force one page reload.
- "CSP violations on every inline script": wrap inline scripts in a Script fragment. See the CSP fragments section.
- "A V1 component I added
#[HandleBackwardsCompatibility]to still behaves V3": the attribute import is most likely wrong. The correct namespace isMagewirephp\Magewire\Features\SupportMagewireBackwardsCompatibility\HandleBackwardsCompatibility. - "
$this->emitis used by migrated code": the current base component still carries the deprecated helper, but new code should use$this->dispatch()so it does not depend on a compatibility trait that may be removed later. - "My observer listening on
message.sentstopped firing": the JS-side hook is nowcommit. For server-side observer events see the table in Features. - "My Feature's
provide()is never called": confirm it is an item onMagewirephp\Magewire\Featuresin the active area'setc/frontend/di.xmloretc/adminhtml/di.xml. A global array addition can be replaced by the later area-specific DI configuration. - "Entangle spams the network tab": the call is still live-by-default from BC. Remove the
#[HandleBackwardsCompatibility]attribute once the component is migrated, then audit everyentangleon that component.
Verifying the upgrade
After migrating, confirm the end state:
- No V1 directive lingers.
- No V1 hook name lingers. Search JS for
message.sent,component.initialized,element.updating,element.removed. - No V1 PHP helper lingers. Search for
->emit(,->emitUp(,->emitSelf(,->emitTo(,->dispatchBrowserEvent(,->getPublicProperties(,$this->idaccesses. - No area-scoped service collection is registered globally. Inspect each match. Move only Features, Mechanisms, Hooks, Synthesizers, and Resolvers that extend area-scoped collections. Keep valid global preferences, plugins, and constructor configuration where they belong.
- No component still carries
#[HandleBackwardsCompatibility]after migration. A repo-wide grep proves the migration is complete. - Site health. Click through the top ten most-trafficked Magewire components, watching the browser devtools Network tab for 4xx/5xx on
/magewire/updateand the console for CSP violations or Alpine warnings.
Rolling back
If the upgrade surfaces a showstopper bug, you can roll back to V1 by restoring the previous composer.lock, running composer install, and flushing caches. Any V3-specific code (the #[HandleBackwardsCompatibility] attribute, ->dispatch()->to() chains, #[On] attributes) will need to be reverted first; V1 will not understand them. Keep the migration PR small enough that the revert is reasonable.
Migration checklist
Copy this into a PR description.
- [ ] PHP is on 8.2+ and the Magento or Mage-OS release is supported by the selected Magewire tag.
- [ ] Composer updated to
magewirephp/magewire:^3.0. - [ ]
magewirephp/magewire-admininstalled if the site uses Magewire in admin. - [ ] The theme compatibility package ensures only one Alpine runtime starts on pages with and without Magewire components.
- [ ] Every existing component carries
#[HandleBackwardsCompatibility](imported fromMagewirephp\Magewire\Features\SupportMagewireBackwardsCompatibility\). - [ ]
wire:model.defer→wire:model. - [ ]
wire:model.lazy→wire:model.blur. - [ ] Bare
wire:model→wire:model.livewhere instant sync is required. - [ ]
wire:model.delay.Xms→wire:model.live.debounce.Xms. - [ ] Every
$wire.entangle('…')audited:.liveadded where needed. - [ ] Every
protected $listeners = […]replaced with#[On('event-name')]attributes. - [ ] Every
$this->emit*()migrated to$this->dispatch()(with->self()or->to()where appropriate; events bubble without->up()). - [ ] Every
$this->getPublicProperties()migrated to$this->all(). - [ ] Every
$this->idaccess migrated to$this->id()/$this->getId(). - [ ] Every deprecated JS hook name (
message.sent,component.initialized,element.updating,element.removed) migrated to the V3 name. - [ ] Every
component.data/component.deferredActionsJS access migrated tocomponent.$wire/component.queuedUpdates. - [ ] Every area-scoped Feature / Mechanism / Hook / Synthesizer / Resolver collection registration moved from global
etc/di.xmltoetc/frontend/di.xml(andetc/adminhtml/di.xmlas needed). - [ ] Inline
<script>tags wrapped in Script fragments where CSP compliance matters. - [ ] Custom
HydratorInterfaceimplementations rewritten as Synthesizers. - [ ]
render(): stringmethods replaced byrendering()hooks that callmagewireBlock()->setTemplate(...). - [ ] BC attribute set to
enabled: false(or removed) on fully migrated components. - [ ] Smoke-tested against the top-trafficked components (devtools Network + console).
Migration done. Remove obsolete BC attributes and any package used only for the legacy integration when every module on the site has reached this state.
Related
- Backwards compatibility: the BC system in depth.
- Hyvä Checkout BC: explicit opt-in and the current fallback limitation.
- Admin → Installation: install the admin companion package.
- Features: how to rewrite V1 Features as V3 Component Hooks.
- Synthesizers: replacement for V1 hydrators.