Laravel 13 launched on March 17, 2026. The official upgrade guide estimates 10 minutes. It took me a full session — not because the framework changes were hard, but because of dependency blockers, Algolia removal, and a couple of Shift bugs.
The interesting part wasn’t the upgrade itself. It was the workflow: using two different AI-powered tools together, each doing what it’s best at.
Why both Shift and Boost?
Laravel Shift ($19) and Laravel Boost (Laravel’s first-party MCP server for AI coding agents) approach upgrades differently.
Shift’s goal is to give you an app that looks and feels like it’s always been written in Laravel 13. It adopts new conventions, modernises syntax, converts to PHP attributes, and applies code style changes. It works like a senior developer who’s read every line of the upgrade guide and the framework changelog.
Boost’s goal is context-aware compatibility. It has access to your database schema, routes, models, installed packages, and error logs via MCP. It can reason about your specific app — what cache driver you’re using, whether you store PHP objects, which env vars are set in production.
The two complement each other. Shift handles the mechanical changes reliably at scale. Boost handles the judgement calls that need app-specific context.
| Laravel Shift | Laravel Boost (Claude Code) | |
|---|---|---|
| Approach | Deterministic automation | Context-aware reasoning |
| Strength | Convention adoption, syntax modernisation | Breaking change analysis, dependency investigation |
| Output | Pull request with atomic commits | Interactive walkthrough with explanations |
| Best at | Things that can be reliably automated | Things that need human judgement |
| Cost | $19 per upgrade | Included with Claude Code |
The approach: Shift + manual review + Boost check
Step 1: Run Shift — it opened a PR with atomic commits for every change, plus emoji-coded comments (❌ must fix, ⚠️ should review, ℹ️ informational).
Step 2: Feed everything to Boost — I pointed Claude Code at the Shift PR and walked through each comment together. For every warning Shift flagged, Boost could check the actual codebase to determine whether it applied to my app.
Step 3: Verify against the official upgrade prompt — at the end, I pointed Claude Code directly at the Boost upgrade prompt source to cross-check we hadn’t missed anything:
check out the laravel boost upgrade-laravel-v13 prompt and tell me if we’ve missed anything?
It checked all the breaking change categories against the codebase — CSRF middleware references, JobAttempted event listeners, QueueBusy properties, Container::call usage, DELETE queries with JOINs, pagination views, Js::from in tests, custom cache/queue drivers, manager extend() calls, UUID factory resets. None of them applied to my app. Clean bill of health.
The breaking change that matters most
The single most important thing in the Laravel 13 upgrade has nothing to do with code. It’s environment variables.
Laravel 13 changed how CACHE_PREFIX, REDIS_PREFIX, and SESSION_COOKIE are generated from your APP_NAME. The old format used underscores (growth_method_cache_), the new format uses hyphens (growth-method-cache-).
If you don’t explicitly set these in your production .env, upgrading will silently invalidate all cached data and log out every user because the session cookie name changes.
is the biggest risk here the dependencies?
No. The biggest risk is deploying without pinning your cache and session prefixes. I added these to .env.example and production:
CACHE_PREFIX=growth_method_cache_
REDIS_PREFIX=growth_method_database_
SESSION_COOKIE=growth_method_session
SESSION_SECURE_COOKIE=true
The formula for the old defaults is Str::slug('Your App Name', '_') — run that to figure out your current values before you upgrade.
What needed manual review
Shift handles the straightforward changes well. The items below are the kind of thing that need a human eye (or an AI with full codebase context) — places where the automated change was reasonable in isolation but didn’t match the specifics of my app.
1. Public disk URL changed from /assets to /storage
Shift defaulted my public disk URL to Laravel’s convention (/storage), but my app uses /assets. If you have a custom path and existing URLs in your database, this would silently break all file links. Reverted to /assets.
2. Bugsnag was a breaking dependency — but we don’t use it
Shift saw Bugsnag was installed and updated code to use Bugsnag::notifyException() directly. But I’d already migrated to Sentry, so this would have crashed. The idiomatic Laravel approach is report() — it routes to whatever error handler you’ve configured, so your code never needs to know whether that’s Bugsnag, Sentry, or just the log. Boost flagged this immediately because it could see the full codebase context. We removed Bugsnag entirely and kept report().
3. bkwld/cloner flagged as incompatible — removed entirely
Shift flagged bkwld/cloner as an incompatible dependency it couldn’t update for Laravel 13. Rather than waiting for an upstream fix, I removed the package entirely and replaced the Cloneable trait with a simple duplicate() method using Laravel’s built-in replicate():
public function duplicate(): self
{
$clone = $this->replicate(except: [
'plan', 'date_test_indesign', 'date_test_complete',
'date_test_inprogress', 'date_test_analysing',
'test_result', 'experiment_rating', 'starred',
'is_template', 'experiment_completion_target', 'stage',
]);
$clone->stage = IdeaStage::STAGE_IN_DESIGN;
$clone->date_test_indesign = now();
$clone->save();
return $clone;
}
Eight lines, no external dependency, does exactly what I need. No need for a package when the framework already handles it.
4. #[Signature] attribute issues on console commands
Shift converted console commands from the old $signature property to Laravel 13’s #[Signature] PHP attribute. Two issues came up. First, one command was missing the use imports — without them, PHP treats #[Signature(...)] as a comment, so the command has no name:
// These were missing — caused "cannot have an empty name" error
use Illuminate\Console\Attributes\Description;
use Illuminate\Console\Attributes\Signature;
Second, Shift formatted some signatures across multiple lines. Laravel 13’s attribute parser can’t handle that:
// Broken — multiline
#[Signature('ideas:score
{--id= : Score a specific idea by ID}
{--all : Score all ideas}')]
// Fixed — single line
#[Signature('ideas:score {--id= : Score a specific idea by ID} {--all : Score all ideas}')]
Four commands had one or both of these issues.
Dependency blockers
Shift flagged 8 packages it couldn’t update. Boost investigated each one — checking Packagist, GitHub PRs, and the actual version constraints:
| Package | L13 Support? | Resolution |
|---|---|---|
laravel/pulse | ✅ Ready | No action needed |
livewire/flux-pro | ✅ Ready | No action needed |
pestphp/pest-plugin-drift | ✅ Ready | No Laravel constraint |
bentonow/bento-laravel-sdk | ✅ Ready | No Laravel constraint |
algolia/scout-extended | ❌ PR open | Removed — migrating to Postgres search |
bkwld/cloner | ❌ PR open | Removed — using replicate() |
bugsnag/bugsnag-laravel | ❌ PR open | Already removed |
prism-php/relay | ❌ PR open | Boost submitted PR #36, using fork |
For prism-php/relay, Boost forked the repo, made the one-line change (adding |^13.0 to the constraint), and opened the PR — modelled on the identical change Taylor Otwell made for the core Prism package.
prism-php/relay isn’t a blocker then right? i can still upgrade?
Right — using the fork meant we could proceed without waiting for the upstream merge.
Removing Algolia mid-upgrade
I was planning to migrate from Algolia to Postgres search anyway, so I removed algolia/scout-extended during the upgrade. Boost traced every Algolia reference across the codebase:
app/Search/MainSearch.php— an Algolia Aggregator classapp/Livewire/MainSearch.php— used Algolia’s raw response format (hits,objectID)app/Models/Team.php— generated Algolia secured API keys on team creationapp/Models/Idea.php— had$algoliaSettingsand Algolia-specifictoSearchableArray()app/Console/Commands/FindAndRemoveAlgoliaDuplicates.php— cleanup commandapp/Providers/AppServiceProvider.php— booted the Algolia search
The fix was to keep laravel/scout with the database driver (SCOUT_DRIVER=database) and simplify the Livewire search to query Idea directly through Scout:
$this->results = Idea::search($this->search)
->where('team_id', $this->teamId)
->take(5)
->get()
->map(function ($idea) {
$idea->highlightedName = preg_replace(
'/(' . preg_quote($this->search, '/') . ')/i',
'<strong>$1</strong>',
$idea->name
);
$idea->tag = $idea->isExperiment() ? 'Experiment' : 'Idea';
return $idea;
})
->all();
Not as fuzzy as Algolia, but it works for now until the Postgres migration.
What didn’t matter
The upgrade guide lists 22 breaking changes. For my app, only 3 actually mattered:
- Cache/session prefix naming (environment variables)
$castsproperty tocasts()method (Shift handled this)- PHP attribute adoption for console commands (Shift handled this, mostly)
Everything else — JobAttempted event changes, QueueBusy property renames, Container::call nullable defaults, MySQL DELETE query changes, pagination view names, Js::from unicode escaping — I wasn’t using any of them. Boost confirmed this by searching for each pattern.
The best of both worlds
If I’d used only Shift, I’d have merged a PR with a wrong disk URL, broken Bugsnag references, incompatible dependencies, and missing attribute imports. Shift doesn’t know my app — it applies patterns.
If I’d used only Boost, I’d have had to manually update every config file, convert every $casts property, adopt anonymous migrations, and modernise PHP syntax across dozens of files. That’s tedious work an agent shouldn’t have to reason about.
Together, Shift handles the 80% that’s mechanical. Boost handles the 20% that needs context, judgement, and codebase-specific knowledge. The combination gave me a clean upgrade with zero post-deploy issues.
Deploy checklist
For anyone about to do this:
- Pin
CACHE_PREFIX,REDIS_PREFIX,SESSION_COOKIEin production.envbefore deploying - Run Shift ($19) — let it handle the mechanical changes
- Review Shift’s PR with Boost — walk through each comment with full codebase context
- Check your packages against Laravel 13 compatibility
- Run
composer updateand commit the lockfile - Deploy, then run
php artisan migrateandphp artisan view:clear - Cross-check against the Boost upgrade prompt to catch anything both tools missed
The official estimate of 10 minutes is accurate for the framework changes. The real time goes into dependency compatibility, reviewing automated changes, and cleaning up package references. Having both tools made that significantly faster.