Start Here
Upgrade Guide
How to move between Wire versions safely and where to find breaking changes.
On this page
How to move between Wire versions safely and where to find breaking changes.
Versioning
The Wire ecosystem ships as four packages — wire-core, wire-forms,
wire-table, wire-sortable — released together from one monorepo, so their
versions move in lockstep. Install or constrain them as a set.
Wire is currently in the 0.x line. Per common pre-1.0 convention, minor
releases may contain breaking changes, so pin a version you have tested and read
the changelog before bumping:
// composer.json"require": { "nyoncode/wire-core": "^0.1", "nyoncode/wire-forms": "^0.1", "nyoncode/wire-table": "^0.1", "nyoncode/wire-sortable": "^0.1"}
Requirements
| Dependency | Supported |
|---|---|
| PHP | 8.2, 8.3, 8.4 |
| Laravel | 12.61+, 13.12+ |
| Livewire | 3.x |
| Tailwind CSS | 3.x or 4.x |
nyoncode/laravel-package-toolkit |
^2.4 |
Confirm your app meets these before upgrading.
Dependency floors (1.17)
Laravel 10 and 11 are gone. 1.17 moved the JavaScript bundles from a package
route to real files under public/vendor, and the code that mirrors them lives in
nyoncode/laravel-package-toolkit — next to the hasAssets() declaration and the
publish tag it is the read side of. The toolkit is on illuminate/support ^12.61.1|^13.12.0,
and a dependency's floor is your floor: an app below it cannot resolve the Wire
packages, whatever the ^12.0 in their own composer.json says. Upgrade Laravel
first, then Wire.
The toolkit constraint is ^2.4. You do not require it directly, so in the
normal case composer update "nyoncode/wire-*" moves it with everything else and
there is nothing to do. It only becomes visible in two shapes:
- your
composer.jsonnamesnyoncode/laravel-package-toolkit— from building your own package on it, or from an old pin — and holds it below 2.4. Composer reports the Wire packages as uninstallable rather than the toolkit as too old, so widen that constraint to^2.4first. - you run Octane. The per-worker asset memo is flushed on
RequestTerminatedthrough the toolkit'sPublishedAssets::flush(), which 2.4 is the first release to carry. Below it, a worker that survives a deploy keeps emitting the previous release's?id=<mtime>andwire:navigatenever notices the new bundles.
Upgrade Steps
-
Read the changelog. Check
CHANGELOG.mdfor the versions you are crossing, especially any Breaking Changes section. -
Update the packages.
composer update "nyoncode/wire-*" -
Re-check published files. If you published config, views, or translations, your copies do not update automatically. Diff them against the new package versions and merge any relevant changes:
config/wire-*.phpresources/views/vendor/wire-*/…lang/vendor/wire-*/…
The fewer views you override, the less there is to reconcile here — see Theming → Overriding Views.
-
Clear caches and rebuild assets.
php artisan view:clearphp artisan config:clearnpm run build -
Run your test suite. A test suite is the fastest way to catch a breaking change in your own forms and tables.
Selection and keyboard gestures
A table's selection grew from a column of checkboxes into a full gesture surface (see Selecting Rows). Four things to check on the way up.
1. Every row gesture is opt-in — ->gestures(). The selection grew a full
gesture surface: Shift/mod clicks for ranges, a drag down the checkbox column
that sweeps a block in, and from the keyboard the arrows, Space,
Shift+arrows and mod+A. None of it is on unless a table asks, because each
changes how the table answers a visitor who never meant to operate it — the rows
go into the tab order, an active row is marked, a drag starts selecting, and a
modified click stops meaning a click.
Add one call to the tables that want it:
->gestures()->selectable()
or, for a project where every table is a back-office table:
// config/wire-table.php'defaults' => ['gestures' => true],
What is not affected: the checkboxes, both select-all controls and the bulk bar work with no change on your side, and a table that never asked mounts no delegated controller at all. So do the right-click row menu and the fill handle, each of which you already had to ask for. See The Gesture Layer for the six capabilities and how to mix them.
2. ->onKey() on a navigation key now throws. It used to be dropped
silently, so the action simply never fired. If a table binds one of these, the
binding was already dead code — rebind it to a free key:
Enter Space ArrowUp ArrowDown Home End PageUp PageDown ContextMenu F10 ?
Backspace stays available, and now doubles as an alias of Delete.
3. Range gestures no longer leave "all matching" mode. When a selection is
"everything the filter matches", the stored list is the set of exclusions — so
a Shift+arrow range over it now deselects that range instead of collapsing
the whole selection down to one page. If your code reads the selection directly,
note that getSelectedRecordKeys() returns [] in that mode by design; use
selectedRecordsQuery() or eachSelectedRecord() instead.
4. Republish the table view if you have overridden it. The gestures need
markup the packaged JavaScript looks for, and a published copy of
resources/views/vendor/wire-table/tables/index.blade.php will not have it. The
view carries a contract marker so a stale copy fails loudly in the browser
console rather than selecting the wrong rows in silence:
php artisan vendor:publish --tag=wire-table::views --force
Re-apply your customisations on top of the new file. If you overrode the view only to restyle it, Theming is usually the smaller path.
5. Behaviour-only record actions now render as buttons on a mobile card. A
phone has no double click, no right click and no hover to discover either, so an
action bound only to a gesture used to be unreachable once the table stacked.
It is now rendered as an ordinary button on the card — and only there; the
desktop table is unchanged. Nothing is doubled: an action already in
->actions(), or one promoted with ->alsoInRowActions(), still yields exactly
one button, and the fallback buttons count towards
->collapseActionsOnMobile(). Opt out per table:
->recordActionButtonsOnMobile(false)
JavaScript assets
Wire's Alpine controllers are now declared by each package and can be emitted from one place in your layout. Two things to do on the way up.
1. Add @wireStackScripts to the layout <head>.
<head> @vite(['resources/css/app.css', 'resources/js/app.js']) @livewireStyles @wireStackScripts </head>
It is additive — every surface still loads its own bundle, so an app without the
directive keeps working. But it is what fixes components dying after a
wire:navigate visit (wireRecordSelection is not defined, dead dropdowns, a grey
scrim over the table): Livewire's cached Back/Forward path does not wait for newly
injected <head> scripts, and only a bundle that was already in the document is
immune. See
Getting Started → JavaScript Assets.
If your app previously worked around this by @include-ing package partials in its
layout, delete those includes and use the directive instead — the partial paths are
internal and the directive dedupes with them anyway.
2. window.Sortable is no longer provided. SortableJS is compiled into the
wire-sortable bundle, so config('wire-sortable.sortablejs_cdn') now defaults to
null and no CDN script is loaded. Reordering is unaffected — the drag controller
uses the bundled copy and never reads the global.
Only your own code is affected, if it relied on that global existing. Either ask for the script back:
// config/wire-sortable.php'sortablejs_cdn' => 'https://cdn.jsdelivr.net/npm/sortablejs@1.15.6/Sortable.min.js',
or bundle SortableJS yourself:
// resources/js/app.jsimport Sortable from 'sortablejs';window.Sortable = Sortable;
Nothing else changes: the config key still works when set, and applications that already set it are unaffected.
Finding Breaking Changes
CHANGELOG.md is the source of truth. Breaking changes are called out under a
Breaking Changes heading per release, often with a before/after migration
table. For example, the 0.1.0 release moved actions and notifications from
NyonCode\WireTable\… to NyonCode\WireCore\…; the changelog lists each moved
class so you can update use statements with a find-and-replace.
If a class or method referenced in these docs no longer exists after an upgrade,
it was likely moved or renamed — search CHANGELOG.md for the old name.
See Also
- Getting Started — requirements and install
- Configuration — publishable config
- Theming — keeping view overrides minimal
- Troubleshooting — issues that appear after an update