Skip to content

Upgrading to Evolve v2.0

Evolve v2.0 is a major release that combines three changes:

  1. Monorepo: All 11 packages consolidated into a single racerfish/evolve package
  2. Livewire 3: Full migration from Livewire 2 to 3
  3. Laravel 12: Framework upgrade from Laravel 10 to 12

What Changed

Before (v1.x)After (v2.0)
11 separate Composer packages1 unified racerfish/evolve package
Livewire 2 (wire:model.defer, $this->emit())Livewire 3 (wire:model, $this->dispatch())
Laravel 10Laravel 12
Carbon 2 (formatLocalized())Carbon 3 (isoFormat())
Multiple VCS repositories1 VCS repository (racerfish/evolve-cms)

Laravel 12 is required

All of Evolve v2.x requires Laravel 12. Skip Laravel 11 during the upgrade. If you land on Laravel 11, Composer will silently resolve racerfish/evolve:^2.0 to 2.0.2 because 2.1.0+ pulls in laravel/ai ^0.4 which requires Laravel 12. The v2.0.x line also has a known UI bug (an Alpine 2 vs. Livewire 3 @entangle version skew) that breaks search inputs and resource filters in the backend. Going directly to Laravel 12 avoids both issues.

If your composer update stops at 2.0.x, confirm with composer why-not racerfish/evolve 2.1.1. A laravel/framework entry in the output means the framework upgrade is still pending.

Prerequisites

  • PHP 8.3 or higher
  • MySQL 8.0+ or PostgreSQL 13+
  • Back up your database before upgrading
  • Ensure all pending migrations have been run: php artisan migrate:status
  • Review any published Livewire components in app/Livewire/ or app/Http/Livewire/ for LW2 patterns

Step 1: Update Composer Repositories

Replace any individual VCS repositories in your composer.json with the single monorepo:

json
"repositories": [
    {
        "type": "vcs",
        "url": "git@github.com:racerfish/evolve-cms.git"
    }
]

Remove all old repository entries pointing to racerfish/evolve-core, racerfish/evolve-blog, racerfish/evolve-theme, etc.

Step 2: Swap Packages

Remove all individual packages and require the unified package:

sh
composer remove \
    racerfish/blog \
    racerfish/company \
    racerfish/devtools \
    racerfish/event \
    racerfish/faq \
    racerfish/locator \
    racerfish/project \
    racerfish/scout24 \
    racerfish/shopify \
    racerfish/theme

composer require racerfish/evolve:^2.0

TIP

You only need to remove the packages that your instance currently has installed. If you don't use racerfish/scout24, skip removing it.

INFO

The ^2.0 caret constraint resolves to >=2.0.0 <3.0.0, so future minor releases (2.1, 2.2, etc.) and patch updates are picked up automatically when you run composer update racerfish/evolve. The version is not pinned to 2.0.

Step 3: Update Laravel Framework

Update your composer.json to require Laravel 12 and its compatible dependencies:

json
"require": {
    "php": "^8.3.2",
    "laravel/framework": "^12.0",
    "racerfish/evolve": "^2.0"
}

Also update any third-party packages that don't support Laravel 12. Common ones:

Packagev1.x (L10)v2.x (L12)
sentry/sentry-laravel^3.4^4.0
nunomaduro/collision^7.0^8.0
larastan/larastannunomaduro/larastan ^2.2^3.0
spatie/laravel-ignition^2.0^2.0 (compatible)

For example, if your project uses Sentry:

json
"require": {
    "sentry/sentry-laravel": "^4.0"
}

And in require-dev:

json
"require-dev": {
    "nunomaduro/collision": "^8.0",
    "larastan/larastan": "^3.0"
}

WARNING

Run composer why-not laravel/framework 12.0 to identify any packages blocking the upgrade. The error message will show which package constrains illuminate/support to L11 or earlier. Apply the same check for composer why-not racerfish/evolve 2.1.1 to confirm the framework is the only blocker.

Step 4: Remove the Livewire Assets Publish Script

In your composer.json, remove the livewire:assets publish line from post-autoload-dump. Livewire 3 handles assets automatically.

json
"scripts": {
    "post-autoload-dump": [
        "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
        "@php artisan package:discover --ansi"
    ]
}

Step 5: Update dont-discover

Simplify the dont-discover list. The monorepo only needs one entry:

json
"extra": {
    "laravel": {
        "dont-discover": [
            "racerfish/evolve"
        ]
    }
}

Step 6: Add Livewire 3 Configuration

Add legacy_model_binding to config/livewire.php:

php
// config/livewire.php
return [
    'legacy_model_binding' => true,

    'temporary_file_upload' => [
        'rules' => 'file|max:50000',
    ],
];

WARNING

The legacy_model_binding setting is required for Evolve's wire:model="item.property" pattern to work in Livewire 3. Do not remove this setting.

Step 7: Migrate Frontend Template (Livewire Directives)

Livewire 3 removed the @livewireStyles and @livewireScripts Blade directives. If your theme layout uses these, you will get:

Call to undefined method Livewire\LivewireManager::scripts()

Fix for frontend layouts

In your theme's main layout (typically theme/views/frontend.blade.php or similar), replace the old Blade directives with direct calls to the FrontendAssets class:

blade
{{-- Before (LW2) --}}
<head>
    ...
    @livewireStyles
</head>
<body>
    ...
    @livewireScripts
</body>

{{-- After (LW3) --}}
<head>
    ...
    {!! \Livewire\Mechanisms\FrontendAssets\FrontendAssets::styles() !!}
</head>
<body>
    ...
    {!! \Livewire\Mechanisms\FrontendAssets\FrontendAssets::scripts() !!}
</body>

WARNING

Do NOT use @livewireScriptConfig as a replacement. That directive only outputs the config JSON and suppresses auto-injection of the actual livewire.js script, which means Alpine.js (bundled with Livewire 3) will never load. Use FrontendAssets::scripts() which includes both the JS file and the config.

TIP

For frontend theme layouts that only occasionally render Livewire components (e.g., contact forms), you can alternatively rely on auto-injection: remove all Livewire directives entirely, and Livewire 3 will auto-inject its assets into any response with </html> that renders a Livewire component. However, explicit inclusion via FrontendAssets is more reliable.

If your theme previously used the cookieconsent pattern to prevent consent managers from blocking Livewire:

blade
{{-- Before (LW2) --}}
{!! str_replace('<script', '<script data-cookieconsent="ignore"', \Livewire\Livewire::scripts()) !!}

This no longer works because Livewire::scripts() was removed from the facade. In LW3, use the static method on the FrontendAssets class and disable auto-injection:

php
// config/livewire.php
return [
    'inject_assets' => false,  // disable auto-injection so we control the output
    'legacy_model_binding' => true,
    // ...
];
blade
{{-- After (LW3) --}}
{!! str_replace('<script', '<script data-cookieconsent="ignore"', \Livewire\Mechanisms\FrontendAssets\FrontendAssets::styles()) !!}
{!! str_replace('<script', '<script data-cookieconsent="ignore"', \Livewire\Mechanisms\FrontendAssets\FrontendAssets::scripts()) !!}

Place styles in <head> and scripts before </body> (same positions as before). When inject_assets is false, you are fully responsible for including both styles and scripts.

WARNING

When using inject_assets => false, the @livewireScriptConfig directive is not needed. The FrontendAssets::scripts() call already includes the config. Do not use both.

Published backend views

If you have published evolve backend views (in theme/views/vendor/evolve/), check for @livewireStyles and @livewireScripts in app.blade.php and blank.blade.php and apply the same replacement to @livewireScriptConfig.

Step 8: Publish the Module Configuration

Add the modules array to your config/evolve.php to control which modules are loaded:

php
// config/evolve.php
return [
    'modules' => [
        'blog'     => true,
        'company'  => true,
        'devtools' => true,
        'event'    => true,
        'faq'      => true,
        'locator'  => true,
        'project'  => true,
        'scout24'  => true,
        'shopify'  => true,
    ],

    // ... rest of your existing config
];

Set any unused modules to false to prevent loading their routes, migrations, and views.

Step 9: Migrate Published Livewire Components

If you have published Livewire components in app/Http/Livewire/ or app/Livewire/, update them for Livewire 3:

Namespace change

php
// Before
namespace App\Http\Livewire\Blocks\Blog;

// After
namespace App\Livewire\Blocks\Blog;

Move the files from app/Http/Livewire/ to app/Livewire/ accordingly.

Wire model syntax

blade
{{-- Before (LW2) --}}
<input wire:model.defer="search" />

{{-- After (LW3) --}}
<input wire:model="search" />

In Livewire 3, wire:model is deferred by default. Use wire:model.live for instant reactivity.

Event dispatch

php
// Before (LW2)
$this->emit('eventName', $data);
$this->emitUp('eventName', $data);

// After (LW3)
$this->dispatch('eventName', data: $data);

All dispatch calls must use named parameters.

Form submit

blade
{{-- Before (LW2) --}}
<form wire:submit.prevent="save">

{{-- After (LW3) --}}
<form wire:submit="save">

Computed properties

php
// Before (LW2)
public function getItemsProperty() { ... }

// After (LW3)
use Livewire\Attributes\Computed;

#[Computed]
public function items() { ... }

Query string

php
// Before (LW2)
protected $queryString = ['search' => ['except' => '']];

// After (LW3)
use Livewire\Attributes\Url;

#[Url(except: '')]
public string $search = '';

Entangle

blade
{{-- Before (LW2) --}}
query: @entangle('search').defer,

{{-- After (LW3) --}}
query: @entangle('search'),

In Livewire 3, @entangle is deferred by default. Use @entangle('search').live for instant sync.

Step 10: Update Date Formatting (Carbon 3)

Laravel 12 ships with Carbon 3, which removed formatLocalized(). Replace with isoFormat():

php
// Before
$date->formatLocalized('%d. %B %Y');

// After
$date->isoFormat('DD. MMMM YYYY');

Common format conversions:

strftimeICU (isoFormat)
%dDD
%mMM
%YYYYY
%BMMMM
%Adddd
%H:%MHH:mm

Step 11: Clean Up Published Migrations

If you previously published package migrations to database/migrations/, remove the copies. The monorepo loads migrations directly from the package.

Check for files like create_blog_*, create_shopify_*, create_faq_* in your migrations directory and delete them if they are copies of package migrations.

Step 12: Update Envoy Import Path

If you use Laravel Envoy for server tasks, update the import path in your Envoy.blade.php to reflect the monorepo structure:

blade
{{-- Before (v1.x) --}}
@import('vendor/racerfish/evolve/bin/Envoy.blade.php')

{{-- After (v2.0) --}}
@import('vendor/racerfish/evolve/core/bin/Envoy.blade.php')

The Envoy tasks file moved into the core/ subdirectory as part of the monorepo consolidation.

Step 13: Clear Caches and Run Migrations

sh
composer install
php artisan optimize:clear
php artisan migrate

Step 14: Rebuild Assets

sh
yarn backend:install
yarn backend:build
yarn theme:install
yarn theme:build

Step 15: Verify

sh
# Check the app boots
php artisan about

# Verify routes load
php artisan route:list

# Check migration status
php artisan migrate:status

# Run static analysis
php artisan analyse

Visit your backend panel and verify:

  • Login works
  • Dashboard loads
  • Page editor functions (block CRUD, media picker)
  • Navigation manager works
  • Forms and leads display correctly

Staying on v1.x (Legacy)

If a client site cannot be upgraded to Livewire 3 (e.g., custom frontend components that depend on LW2 patterns), you can stay on the legacy version:

json
"repositories": [
    {
        "type": "vcs",
        "url": "git@github.com:racerfish/evolve-cms.git"
    }
]
sh
composer require racerfish/evolve:v1.99

The v1.99 tag points to the last pre-Livewire 3 release with the monorepo structure. This version receives critical bug fixes only.

Updating After the Migration

Future v2.x updates (2.1, 2.2, etc.) are a single command:

sh
composer update racerfish/evolve
php artisan migrate

Because the composer constraint is ^2.0, the command will bring in any new 2.x minor or patch release automatically. A major release (3.x) will require an explicit constraint bump and its own upgrade guide.

Troubleshooting

"Unable to locate file: vendor/racerfish/evolve/bin/Envoy.blade.php"

The Envoy import path changed in v2.0. Update your Envoy.blade.php to use vendor/racerfish/evolve/core/bin/Envoy.blade.php. See Step 12.

Class not found errors

Run composer dump-autoload to regenerate the autoloader.

Duplicate migration errors

Check for locally published migration files that conflict with package migrations. See Step 11.

Views not found

Clear the view cache: php artisan view:clear. If you have published views in theme/views/vendor/, verify the view namespace paths match (they haven't changed).

Livewire component not found

If you see "Unable to find component" errors, check that the namespace was updated from App\Http\Livewire to App\Livewire and that files were moved accordingly.

"Property not found" on wire:model

Ensure legacy_model_binding => true is set in config/livewire.php. See Step 6

"Call to undefined method LivewireManager::scripts()"

Your theme layout still uses @livewireScripts or @livewireStyles. See Step 7.

composer update stops at racerfish/evolve 2.0.2

Composer resolves ^2.0 against your current framework constraint. 2.1.0+ requires Laravel 12 (via laravel/ai ^0.4), so a project still on Laravel 11 is capped at 2.0.2. Run composer why-not racerfish/evolve 2.1.1 to confirm. Complete Step 3 to move to Laravel 12, then composer update racerfish/evolve will pick up 2.1.x.

Backend search or filter inputs throw "Cannot read properties of undefined (reading 'find')"

This is the v2.0.x Alpine 2 vs. Livewire 3 version skew. Evolve 2.0.x ships Alpine 2 in the backend bundle, but the backend Blade views use Livewire 3's @entangle, which compiles to window.Livewire.find(...).entangle(...). Alpine 2 evaluates x-data on DOMContentLoaded, before window.Livewire exists. The fix is to upgrade to Laravel 12 + Evolve 2.1.x, which moves the backend bundle to Alpine 3. See the warning at the top of this guide.