Skip to content

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.

Livewire Reference

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

  1. Upgrade PHP, Magento, and Composer requirements.
  2. Install V3: composer require magewirephp/magewire:^3.0.
  3. Install or update the appropriate theme compatibility package so Alpine is loaded once.
  4. Add #[HandleBackwardsCompatibility] to every existing component. The site should run as-is.
  5. Migrate one component at a time using the checklist below.
  6. Drop the BC attribute per component once it is fully V3-native.
  7. Remove the BC feature registration when the whole module is migrated.

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.2 or 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 that lets V1 components keep running with minimal changes. Turn it on per component with the #[HandleBackwardsCompatibility] attribute:

use Magewirephp\Magewire\Features\SupportMagewireBackwardsCompatibility\HandleBackwardsCompatibility;

#[HandleBackwardsCompatibility]
class LegacyCart extends \Magewirephp\Magewire\Component
{
    // Component code: no other changes required to keep V1 behaviour.
}

// 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:modelwire:model.live, wire:model.deferwire:model, wire:model.lazywire:model.blur, wire:model.delay.Xmswire: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.datacomponent.$wire and component.deferredActionscomponent.queuedUpdates.
  • Keeps $this->id, $this->getPublicProperties(), and the V1 emit*() family available on the base Component class via the HandlesComponentBackwardsCompatibility trait.

What the BC layer does not do

You still have to manually handle:

  • Component method signature changes (e.g. updating($prop, $value) parameter order).
  • 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 BC shims activate for that component's DOM subtree. Otherwise they stay dormant, so unaffected components pay no runtime cost.

Resolution priority for the flag:

  1. #[HandleBackwardsCompatibility] attribute (either enabled: true or enabled: false): wins in all cases.
  2. Programmatic assignment: store($component)->set('magewire:bc', true) from a Component Hook or Feature.
  3. 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

Below is the exhaustive list. Each section identifies the V1 shape, the V3 shape, and what the BC layer does for you automatically.

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 commitfail() Yes
message.received commitsucceed() Yes
message.processed commitsucceed()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', ...)->up() V1 kept; prefer ->up() chain.
$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->js("window.dispatchEvent(new CustomEvent('e', { detail: … }))") or a frontend #[On] handler V1 kept under BC trait.
$this->getPublicProperties() $this->all() V1 kept under BC trait.
public $this->id $this->id() / $this->getId() Public property only exists in BC trait; removed from non-BC components.

Lifecycle hooks

V3 adds more lifecycle hooks than V1 exposed. All are optional:

Hook When it fires
boot() Every request, before mount/hydrate.
booted() Every request, after boot/hydrate sequence.
initialize() Every request, before mount/hydrate.
mount(array $params) Initial render only.
hydrate() Every subsequent request.
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 but V3 adds per-property variants and a booted() / initialize() pair, useful for DI wiring or authorisation guards.

updating / updated argument order

V1 called updatingFooBar($value) with the new value as the only argument. V3 passes updatingFooBar($value, $oldValue, ...) in some cases. Check the UpdatingUpdatedArgumentSwapResolver BC rule if a V1 hook starts receiving the wrong arguments. It sits in lib/MagewireBc/Features/SupportMagewireBackwardsCompatibility/Resolver/.

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'
    );
}

Area-scoped DI is required

V1 registered Features, Mechanisms, and Hooks in the global etc/di.xml. V3's service provider reads area-scoped DI only; registrations in global DI are ignored.

Move every Magewire-related <type> block into etc/frontend/di.xml (and/or etc/adminhtml/di.xml if the registration should also apply in admin).

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, backed enums, and \Magento\Framework\DataObject. 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:

  • No second Alpine. Any <script src="alpine.min.js"> in your theme must go.
  • 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 into Alpine.data() registrations or a utility on window.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.

grep -rn "wire:model" app/code your-theme/

3. Migrate entangle calls

Grep for $wire.entangle( and decide per call site whether you want .live (instant sync) or deferred (the V3 default).

grep -rn "entangle(" app/code your-theme/

4. Migrate $listeners arrays to #[On]

grep -rn "protected \$listeners" app/code

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(…)->up().
  • $this->emitTo('other', …)$this->dispatch(…)->to('other').
  • $this->getPublicProperties()$this->all().
  • $this->id$this->id() or $this->getId().

7. Move DI registrations

Walk etc/di.xml for any <type name="Magewirephp\Magewire\…"> blocks. Move them into etc/frontend/di.xml (and etc/adminhtml/di.xml if they should also run in admin). Anything Magewire-related in global etc/di.xml is ignored.

8. Drop the BC attribute per component

As each component becomes fully V3-native, switch its attribute to #[HandleBackwardsCompatibility(enabled: false)]. That component no longer pays the BC-shim runtime cost. When every component in the module is V3-native, remove the attribute entirely.

9. Remove the BC feature registration (whole-module)

When every component on the site is migrated, including V1 components in other modules, remove the BC feature registration from your theme compatibility module's DI. The JS shim bundle stops loading.

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 a Magewire 3.5 resolver interaction makes implicit opt-in 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:click stopped 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 is Magewirephp\Magewire\Features\SupportMagewireBackwardsCompatibility\HandleBackwardsCompatibility.
  • "$this->emit throws BadMethodCallException": the component does not use the BC trait (or it extends a class that removed it). Switch to $this->dispatch().
  • "My observer listening on message.sent stopped firing": the JS-side hook is now commit. For server-side observer events see the table in Features.
  • "My Feature's provide() is never called": the Feature is registered in the global etc/di.xml. Move it into etc/frontend/di.xml and/or etc/adminhtml/di.xml.
  • "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 every entangle on that component.

Verifying the upgrade

After migrating, confirm the end state:

  1. No V1 directive lingers.
    grep -rEn "wire:model(\.defer|\.lazy|\.delay)" app/code your-theme/
    
  2. No V1 hook name lingers. Search JS for message.sent, component.initialized, element.updating, element.removed.
  3. No V1 PHP helper lingers. Search for ->emit(, ->emitUp(, ->emitSelf(, ->emitTo(, ->dispatchBrowserEvent(, ->getPublicProperties(, $this->id accesses.
  4. No global Magewire DI remains.
    grep -lrn "Magewirephp\\\\Magewire" app/code/**/etc/di.xml
    
    Anything matching must move to etc/frontend/di.xml or etc/adminhtml/di.xml.
  5. No component still carries #[HandleBackwardsCompatibility] after migration. A repo-wide grep proves the migration is complete.
  6. Site health. Click through the top ten most-trafficked Magewire components, watching the browser devtools Network tab for 4xx/5xx on /magewire/update and 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-admin installed 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 from Magewirephp\Magewire\Features\SupportMagewireBackwardsCompatibility\).
  • [ ] wire:model.deferwire:model.
  • [ ] wire:model.lazywire:model.blur.
  • [ ] Bare wire:modelwire:model.live where instant sync is required.
  • [ ] wire:model.delay.Xmswire:model.live.debounce.Xms.
  • [ ] Every $wire.entangle('…') audited: .live added where needed.
  • [ ] Every protected $listeners = […] replaced with #[On('event-name')] attributes.
  • [ ] Every $this->emit*() migrated to $this->dispatch() (with ->up(), ->self(), ->to() as appropriate).
  • [ ] Every $this->getPublicProperties() migrated to $this->all().
  • [ ] Every $this->id access 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.deferredActions JS access migrated to component.$wire / component.queuedUpdates.
  • [ ] Every Feature / Mechanism / Hook / Synthesizer / Resolver registration moved from global etc/di.xml to etc/frontend/di.xml (and etc/adminhtml/di.xml as needed).
  • [ ] Inline <script> tags wrapped in Script fragments where CSP compliance matters.
  • [ ] Custom HydratorInterface implementations rewritten as Synthesizers.
  • [ ] render(): string methods replaced by rendering() hooks that call magewireBlock()->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. You can now remove the theme BC feature registration when every module on the site has reached this state.