ホーム>Laravel>Laravel 12 & 13 New Features Roundup: Actually Upgrading 11 → 12 → 13
Laravel

Laravel 12 & 13 New Features Roundup: Actually Upgrading 11 → 12 → 13

Thank you for your continued support.
This article contains advertisements that help fund our operations.

A rundown of what's new in Laravel 12 and 13, plus a hands-on record of actually creating a Laravel 11 project and upgrading it to 12, then 13, snags included.

Laravel 13 had just been released, so I read through the official upgrade guide and actually built a Laravel 11 project, then upgraded it in order: 11 → 12 → 13.

The short version: both upgrades were just a matter of editing composer.json per the official guide and running composer update — for a simple setup like this one, no code changes were needed.

That said, actually going through it surfaced a few snags that aren't documented anywhere, so I'm recording them here as-is.

New Features in Laravel 12 & 13

Laravel 12 (released February 2025)

Laravel 12.x Upgrade Guide

According to the official docs, Laravel 12 is a relatively minor release focused on minimizing impact on existing apps.

  • New application starter kits: Refreshed kits for React, Svelte, Vue, and Livewire, now built on Inertia 2, TypeScript, shadcn/ui, and Tailwind (the Livewire kit uses Flux UI and Laravel Volt)
  • WorkOS AuthKit option: A starter kit with social login, passkeys, and SSO support is now available too
  • UUIDs default to v7: The HasUuids trait now generates ordered (v7) UUIDs. If you want to keep v4, you need to switch to HasVersion4Uuids
  • SVG image validation is disallowed by default: To allow it, you now need to explicitly add the image:allow_svg rule
  • The default local disk changed to storage/app/private
  • Supports PHP 8.2–8.5 (bug fixes through August 2025, security fixes through February 2027)

Laravel 13 (released 2026)

Laravel 13.x Upgrade Guide

According to the official docs, Laravel 13 is a release that adds features like the following.

  • A first-party Laravel AI SDK: Text, image, and audio generation, plus embeddings, are now available through a provider-agnostic unified API
  • Built-in JSON:API response support: You can implement resource responses that conform to the JSON:API spec concisely
  • The CSRF protection middleware was renamed: VerifyCsrfToken became PreventRequestForgery, adding origin-aware request verification
  • Queue::route(...): Lets you define the queue and connection for each job class in one place
  • PHP attributes like #[Middleware], #[Authorize], #[Tries], and #[Backoff]: Lets you write routing and queue config more declaratively
  • Cache::touch(...): Extends a cache entry's TTL without changing its value
  • Supports semantic search using PostgreSQL + pgvector
  • The minimum PHP requirement was raised to 8.3 (Laravel 12 supported PHP 8.2+)

I Built Laravel 11 in Docker and Actually Upgraded It to 12, Then 13

Rather than just reading the official guide, I set up a PHP 8.3 environment in Docker, actually created a Laravel 11 project, and genuinely upgraded it in order — 12, then 13.

1. Create the Laravel 11 project

composer create-project "laravel/laravel:^11.0" laravel-upgrade-guide-demo

Running this stopped immediately with an error.

Your requirements could not be resolved to an installable set of packages.

  Problem 1
    - Root composer.json requires laravel/framework ^11.31, found laravel/framework[v11.31.0, ..., v11.56.1]
      but these were not loaded, because they are affected by security advisories
      ("PKSA-m5cs-t1y6-qpcs", "PKSA-3r5d-mb8f-1qw9", "PKSA-mdq4-51ck-6kdq", ...).

This looked like it hit a feature introduced in Composer 2.10.x that blocks installing versions with known security advisories by default.

Running composer audit for details showed 3 advisories against laravel/framework (as of Laravel 11.56.1).

Advisory IDSeverityDetails
PKSA-m5cs-t1y6-qpcsmediumPath confusion in signed temporary URLs
PKSA-3r5d-mb8f-1qw9highCRLF injection in the default email validation rule
PKSA-mdq4-51ck-6kdq (CVE-2026-48019)-Same as above (the CVE-numbered entry)

Since what I wanted to verify here was strictly "the process of upgrading in order starting from 11," I didn't weigh in on the advisories themselves and just temporarily disabled this block.

composer config -g policy.advisories.block false

That let Laravel 11.56.1 install successfully.

php artisan --version
# Laravel Framework 11.56.1

The homepage (the Welcome view) rendered normally too, as you can see here.

laravel upgrade guide 01 welcome

The Security Advisory Block Trips You Up When You Deliberately Install an Old Version

I found that for testing/learning work like this — deliberately starting from an old version and upgrading step by step — you need a workaround like composer config -g policy.advisories.block false.

On the flip side, it also means a normal new project is less likely to end up on a vulnerable version without you noticing, so it's probably best not to disable this carelessly for production use.

2. Upgrade Laravel 11 → 12

Following the official upgrade guide, I edited the require section of composer.json.

- "laravel/framework": "^11.31",
+ "laravel/framework": "^12.0",
composer update --with-all-dependencies
Lock file operations: 1 install, 1 update, 0 removals
  - Upgrading laravel/framework (v11.56.1 => v12.68.0)
  - Locking symfony/polyfill-php84 (v1.38.1)
...
No security vulnerability advisories found.

Only laravel/framework's version went up; the only new dependency added was symfony/polyfill-php84.

The 3 security advisories from earlier were also resolved by 12.68.0, and the output now reads "No security vulnerability advisories found."

php artisan --version
# Laravel Framework 12.68.0

php artisan test
#   PASS  Tests\Unit\ExampleTest
#   PASS  Tests\Feature\ExampleTest
#   Tests:    2 passed (2 assertions)

Since this was a totally fresh project, there was no direct reference to the HasUuids trait or to VerifyCsrfToken, so no code changes were needed at all.

The homepage's appearance didn't change either (the default Blade template itself hasn't changed between versions, so I'm skipping the screenshot).

3. Upgrade Laravel 12 → 13

Next, the upgrade to 13. Following the official guide's instructions, I bumped laravel/tinker and phpunit/phpunit along with laravel/framework.

  "require": {
-     "php": "^8.2",
-     "laravel/framework": "^12.0",
-     "laravel/tinker": "^2.9"
+     "php": "^8.3",
+     "laravel/framework": "^13.0",
+     "laravel/tinker": "^3.0"
  },
  "require-dev": {
      ...
-     "phpunit/phpunit": "^11.0.1"
+     "phpunit/phpunit": "^12.0"
  },
composer update --with-all-dependencies

This time the dependency chain reached much further, dragging guzzlehttp/guzzle (7.15.5 → 8.1.0) and phpunit/phpunit (11.5.56 → 12.5.34) along with it.

  - Upgrading laravel/framework (v12.68.0 => v13.29.0): Extracting archive
  - Upgrading laravel/tinker (v2.11.1 => v3.0.2): Extracting archive
  - Upgrading guzzlehttp/guzzle (7.15.5 => 8.1.0): Extracting archive
  - Upgrading phpunit/phpunit (11.5.56 => 12.5.34): Extracting archive
...
No security vulnerability advisories found.
php artisan --version
# Laravel Framework 13.29.0

php artisan test
#   PASS  Tests\Unit\ExampleTest
#   PASS  Tests\Feature\ExampleTest
#   Tests:    2 passed (2 assertions)

I searched app/, bootstrap/, and routes/ for VerifyCsrfToken and PreventRequestForgery, but neither name was referenced anywhere to begin with, so no code changes were needed here either.

The php Constraint Doesn't Get Bumped Automatically

Looking at Laravel 13's composer.json (more precisely, laravel/framework 13.x's), it requires "php": "^8.3".

grep -A1 '"require"' vendor/laravel/framework/composer.json
# "require": {
#     "php": "^8.3",

But the "php": "^8.2" constraint in my own project's composer.json didn't get rewritten automatically, even after running composer update.

My test environment had PHP 8.3 installed, so this caused no real harm, but if left alone, trying to set this project up fresh on a PHP 8.2 environment would error out during dependency resolution (it's the declaration in composer.json that's out of sync, not the actual PHP version).

After working through the upgrade guide, it seems safest to also peek at the upgraded package's own vendor/.../composer.json and manually update the php constraint to match.

Starting From a Blank Project, 11 → 12 → 13 Really Was Zero Code Changes

With this setup (Blade only, no frontend starter kit, no UUID or CSRF middleware customization), the two-step major version upgrade was complete with nothing more than editing composer.json and running composer update.

That said, this just means I never had any code touching the changed areas in the first place — for a real production app, it's worth cross-checking the upgrade guide's list against whether you're using UUID generation, SVG uploads, CSRF-related customization, cache serialization settings, and so on.

Summary

The positioning difference — Laravel 12 as a "change-light maintenance release" and Laravel 13 as a "feature release that adds the AI SDK and JSON:API support" — came through exactly as described once I actually went through the upgrades myself.

Editing composer.json itself was a few minutes of work, exactly as the official guide describes, but actually running into undocumented behavior like Composer's security advisory block was a payoff that only came from doing the work by hand.

If you're about to upgrade from 11 or 12, check your current advisories with composer audit, and don't forget to update the php constraint too.

Thanks for reading all the way through.

Please Provide Feedback
We would appreciate your feedback on this article. Feel free to leave a comment on any relevant YouTube video or reach out through the contact form. Thank you!