Release Notes
Versioning Scheme
Laravel and its other first-party packages follow Semantic Versioning. Major framework releases are released every year (~February), while minor and patch releases may be released as often as every week. Minor and patch releases should never contain breaking changes.
When referencing the Laravel framework or its components from your application or package, you should always use a version constraint such as ^9.0
, since major releases of Laravel do include breaking changes. However, we strive to always ensure you may update to a new major release in one day or less.
Named Arguments
Named arguments are not covered by Laravel's backwards compatibility guidelines. We may choose to rename function arguments when necessary in order to improve the Laravel codebase. Therefore, using named arguments when calling Laravel methods should be done cautiously and with the understanding that the parameter names may change in the future.
Support Policy
For all Laravel releases, bug fixes are provided for 18 months and security fixes are provided for 2 years. For all additional libraries, including Lumen, only the latest major release receives bug fixes. In addition, please review the database versions supported by Laravel.
Version | PHP (*) | Release | Bug Fixes Until | Security Fixes Until |
---|---|---|---|---|
6 (LTS) | 7.2 - 8.0 | September 3rd, 2019 | January 25th, 2022 | September 6th, 2022 |
7 | 7.2 - 8.0 | March 3rd, 2020 | October 6th, 2020 | March 3rd, 2021 |
8 | 7.3 - 8.1 | September 8th, 2020 | July 26th, 2022 | January 24th, 2023 |
9 | 8.0 - 8.2 | February 8th, 2022 | August 8th, 2023 | February 6th, 2024 |
10 | 8.1 - 8.3 | February 14th, 2023 | August 6th, 2024 | February 4th, 2025 |
(*) Supported PHP versions
Laravel 9
As you may know, Laravel transitioned to yearly releases with the release of Laravel 8. Previously, major versions were released every 6 months. This transition is intended to ease the maintenance burden on the community and challenge our development team to ship amazing, powerful new features without introducing breaking changes. Therefore, we have shipped a variety of robust features to Laravel 8 without breaking backwards compatibility, such as parallel testing support, improved Breeze starter kits, HTTP client improvements, and even new Eloquent relationship types such as "has one of many".
Therefore, this commitment to ship great new features during the current release will likely lead to future "major" releases being primarily used for "maintenance" tasks such as upgrading upstream dependencies, which can be seen in these release notes.
Laravel 9 continues the improvements made in Laravel 8.x by introducing support for Symfony 6.0 components, Symfony Mailer, Flysystem 3.0, improved route:list
output, a Laravel Scout database driver, new Eloquent accessor / mutator syntax, implicit route bindings via Enums, and a variety of other bug fixes and usability improvements.
PHP 8.0
Laravel 9.x requires a minimum PHP version of 8.0.
Symfony Mailer
Symfony Mailer support was contributed by Dries Vints, James Brooks, and Julius Kiekbusch.
Previous releases of Laravel utilized the Swift Mailer library to send outgoing email. However, that library is no longer maintained and has been succeeded by Symfony Mailer.
Please review the upgrade guide to learn more about ensuring your application is compatible with Symfony Mailer.
Flysystem 3.x
Flysystem 3.x support was contributed by Dries Vints.
Laravel 9.x upgrades our upstream Flysystem dependency to Flysystem 3.x. Flysystem powers all of filesystem interactions offered by the Storage
facade.
Please review the upgrade guide to learn more about ensuring your application is compatible with Flysystem 3.x.
Improved Eloquent Accessors / Mutators
Improved Eloquent accessors / mutators was contributed by Taylor Otwell.
Laravel 9.x offers a new way to define Eloquent accessors and mutators. In previous releases of Laravel, the only way to define accessors and mutators was by defining prefixed methods on your model like so:
1public function getNameAttribute($value)2{3 return strtoupper($value);4}56public function setNameAttribute($value)7{8 $this->attributes['name'] = $value;9}
1public function getNameAttribute($value)2{3 return strtoupper($value);4}56public function setNameAttribute($value)7{8 $this->attributes['name'] = $value;9}
However, in Laravel 9.x you may define an accessor and mutator using a single, non-prefixed method by type-hinting a return type of Illuminate\Database\Eloquent\Casts\Attribute
:
1use Illuminate\Database\Eloquent\Casts\Attribute;23public function name(): Attribute4{5 return new Attribute(6 get: fn ($value) => strtoupper($value),7 set: fn ($value) => $value,8 );9}
1use Illuminate\Database\Eloquent\Casts\Attribute;23public function name(): Attribute4{5 return new Attribute(6 get: fn ($value) => strtoupper($value),7 set: fn ($value) => $value,8 );9}
In addition, this new approach to defining accessors will cache object values that are returned by the attribute, just like custom cast classes:
1use App\Support\Address;2use Illuminate\Database\Eloquent\Casts\Attribute;34public function address(): Attribute5{6 return new Attribute(7 get: fn ($value, $attributes) => new Address(8 $attributes['address_line_one'],9 $attributes['address_line_two'],10 ),11 set: fn (Address $value) => [12 'address_line_one' => $value->lineOne,13 'address_line_two' => $value->lineTwo,14 ],15 );16}
1use App\Support\Address;2use Illuminate\Database\Eloquent\Casts\Attribute;34public function address(): Attribute5{6 return new Attribute(7 get: fn ($value, $attributes) => new Address(8 $attributes['address_line_one'],9 $attributes['address_line_two'],10 ),11 set: fn (Address $value) => [12 'address_line_one' => $value->lineOne,13 'address_line_two' => $value->lineTwo,14 ],15 );16}
Enum Eloquent Attribute Casting
Enum casting is only available for PHP 8.1+.
Enum casting was contributed by Mohamed Said.
Eloquent now allows you to cast your attribute values to PHP "backed" Enums. To accomplish this, you may specify the attribute and enum you wish to cast in your model's $casts
property array:
1use App\Enums\ServerStatus;23/**4 * The attributes that should be cast.5 *6 * @var array7 */8protected $casts = [9 'status' => ServerStatus::class,10];
1use App\Enums\ServerStatus;23/**4 * The attributes that should be cast.5 *6 * @var array7 */8protected $casts = [9 'status' => ServerStatus::class,10];
Once you have defined the cast on your model, the specified attribute will be automatically cast to and from an enum when you interact with the attribute:
1if ($server->status == ServerStatus::Provisioned) {2 $server->status = ServerStatus::Ready;34 $server->save();5}
1if ($server->status == ServerStatus::Provisioned) {2 $server->status = ServerStatus::Ready;34 $server->save();5}
Implicit Route Bindings With Enums
Implicit Enum bindings was contributed by Nuno Maduro.
PHP 8.1 introduces support for Enums. Laravel 9.x introduces the ability to type-hint an Enum on your route definition and Laravel will only invoke the route if that route segment is a valid Enum value in the URI. Otherwise, an HTTP 404 response will be returned automatically. For example, given the following Enum:
1enum Category: string2{3 case Fruits = 'fruits';4 case People = 'people';5}
1enum Category: string2{3 case Fruits = 'fruits';4 case People = 'people';5}
You may define a route that will only be invoked if the {category}
route segment is fruits
or people
. Otherwise, an HTTP 404 response will be returned:
1Route::get('/categories/{category}', function (Category $category) {2 return $category->value;3});
1Route::get('/categories/{category}', function (Category $category) {2 return $category->value;3});
Forced Scoping Of Route Bindings
Forced scoped bindings was contributed by Claudio Dekker.
In previous releases of Laravel, you may wish to scope the second Eloquent model in a route definition such that it must be a child of the previous Eloquent model. For example, consider this route definition that retrieves a blog post by slug for a specific user:
1use App\Models\Post;2use App\Models\User;34Route::get('/users/{user}/posts/{post:slug}', function (User $user, Post $post) {5 return $post;6});
1use App\Models\Post;2use App\Models\User;34Route::get('/users/{user}/posts/{post:slug}', function (User $user, Post $post) {5 return $post;6});
When using a custom keyed implicit binding as a nested route parameter, Laravel will automatically scope the query to retrieve the nested model by its parent using conventions to guess the relationship name on the parent. However, this behavior was only previously supported by Laravel when a custom key was used for the child route binding.
However, in Laravel 9.x, you may now instruct Laravel to scope "child" bindings even when a custom key is not provided. To do so, you may invoke the scopeBindings
method when defining your route:
1use App\Models\Post;2use App\Models\User;34Route::get('/users/{user}/posts/{post}', function (User $user, Post $post) {5 return $post;6})->scopeBindings();
1use App\Models\Post;2use App\Models\User;34Route::get('/users/{user}/posts/{post}', function (User $user, Post $post) {5 return $post;6})->scopeBindings();
Or, you may instruct an entire group of route definitions to use scoped bindings:
1Route::scopeBindings()->group(function () {2 Route::get('/users/{user}/posts/{post}', function (User $user, Post $post) {3 return $post;4 });5});
1Route::scopeBindings()->group(function () {2 Route::get('/users/{user}/posts/{post}', function (User $user, Post $post) {3 return $post;4 });5});
Controller Route Groups
Route group improvements were contributed by Luke Downing.
You may now use the controller
method to define the common controller for all of the routes within the group. Then, when defining the routes, you only need to provide the controller method that they invoke:
1use App\Http\Controllers\OrderController;23Route::controller(OrderController::class)->group(function () {4 Route::get('/orders/{id}', 'show');5 Route::post('/orders', 'store');6});
1use App\Http\Controllers\OrderController;23Route::controller(OrderController::class)->group(function () {4 Route::get('/orders/{id}', 'show');5 Route::post('/orders', 'store');6});
Full Text Indexes / Where Clauses
Full text indexes and "where" clauses were contributed by Taylor Otwell and Dries Vints.
When using MySQL or PostgreSQL, the fullText
method may now be added to column definitions to generate full text indexes:
1$table->text('bio')->fullText();
1$table->text('bio')->fullText();
In addition, the whereFullText
and orWhereFullText
methods may be used to add full text "where" clauses to a query for columns that have full text indexes. These methods will be transformed into the appropriate SQL for the underlying database system by Laravel. For example, a MATCH AGAINST
clause will be generated for applications utilizing MySQL:
1$users = DB::table('users')2 ->whereFullText('bio', 'web developer')3 ->get();
1$users = DB::table('users')2 ->whereFullText('bio', 'web developer')3 ->get();
Laravel Scout Database Engine
The Laravel Scout database engine was contributed by Taylor Otwell and Dries Vints.
If your application interacts with small to medium sized databases or has a light workload, you may now use Scout's "database" engine instead of a dedicated search service such as Algolia or MeiliSearch. The database engine will use "where like" clauses and full text indexes when filtering results from your existing database to determine the applicable search results for your query.
To learn more about the Scout database engine, consult the Scout documentation.
Rendering Inline Blade Templates
Rendering inline Blade templates was contributed by Jason Beggs. Rendering inline Blade components was contributed by Toby Zerner.
Sometimes you may need to transform a raw Blade template string into valid HTML. You may accomplish this using the render
method provided by the Blade
facade. The render
method accepts the Blade template string and an optional array of data to provide to the template:
1use Illuminate\Support\Facades\Blade;23return Blade::render('Hello, {{ $name }}', ['name' => 'Julian Bashir']);
1use Illuminate\Support\Facades\Blade;23return Blade::render('Hello, {{ $name }}', ['name' => 'Julian Bashir']);
Similarly, the renderComponent
method may be used to render a given class component by passing the component instance to the method:
1use App\View\Components\HelloComponent;23return Blade::renderComponent(new HelloComponent('Julian Bashir'));
1use App\View\Components\HelloComponent;23return Blade::renderComponent(new HelloComponent('Julian Bashir'));
Slot Name Shortcut
Slot name shortcuts were contributed by Caleb Porzio.
In previous releases of Laravel, slot names were provided using a name
attribute on the x-slot
tag:
1<x-alert>2 <x-slot name="title">3 Server Error4 </x-slot>56 <strong>Whoops!</strong> Something went wrong!7</x-alert>
1<x-alert>2 <x-slot name="title">3 Server Error4 </x-slot>56 <strong>Whoops!</strong> Something went wrong!7</x-alert>
However, beginning in Laravel 9.x, you may specify the slot's name using a convenient, shorter syntax:
1<x-slot:title>2 Server Error3</x-slot>
1<x-slot:title>2 Server Error3</x-slot>
Checked / Selected Blade Directives
Checked and selected Blade directives were contributed by Ash Allen and Taylor Otwell.
For convenience, you may now use the @checked
directive to easily indicate if a given HTML checkbox input is "checked". This directive will echo checked
if the provided condition evaluates to true
:
1<input type="checkbox"2 name="active"3 value="active"4 @checked(old('active', $user->active)) />
1<input type="checkbox"2 name="active"3 value="active"4 @checked(old('active', $user->active)) />
Likewise, the @selected
directive may be used to indicate if a given select option should be "selected":
1<select name="version">2 @foreach ($product->versions as $version)3 <option value="{{ $version }}" @selected(old('version') == $version)>4 {{ $version }}5 </option>6 @endforeach7</select>
1<select name="version">2 @foreach ($product->versions as $version)3 <option value="{{ $version }}" @selected(old('version') == $version)>4 {{ $version }}5 </option>6 @endforeach7</select>
Bootstrap 5 Pagination Views
Bootstrap 5 pagination views were contributed by Jared Lewis.
Laravel now includes pagination views built using Bootstrap 5. To use these views instead of the default Tailwind views, you may call the paginator's useBootstrapFive
method within the boot
method of your App\Providers\AppServiceProvider
class:
1use Illuminate\Pagination\Paginator;23/**4 * Bootstrap any application services.5 *6 * @return void7 */8public function boot()9{10 Paginator::useBootstrapFive();11}
1use Illuminate\Pagination\Paginator;23/**4 * Bootstrap any application services.5 *6 * @return void7 */8public function boot()9{10 Paginator::useBootstrapFive();11}
Improved Validation Of Nested Array Data
Improved validation of nested array inputs was contributed by Steve Bauman.
Sometimes you may need to access the value for a given nested array element when assigning validation rules to the attribute. You may now accomplish this using the Rule::forEach
method. The forEach
method accepts a closure that will be invoked for each iteration of the array attribute under validation and will receive the attribute's value and explicit, fully-expanded attribute name. The closure should return an array of rules to assign to the array element:
1use App\Rules\HasPermission;2use Illuminate\Support\Facades\Validator;3use Illuminate\Validation\Rule;45$validator = Validator::make($request->all(), [6 'companies.*.id' => Rule::forEach(function ($value, $attribute) {7 return [8 Rule::exists(Company::class, 'id'),9 new HasPermission('manage-company', $value),10 ];11 }),12]);
1use App\Rules\HasPermission;2use Illuminate\Support\Facades\Validator;3use Illuminate\Validation\Rule;45$validator = Validator::make($request->all(), [6 'companies.*.id' => Rule::forEach(function ($value, $attribute) {7 return [8 Rule::exists(Company::class, 'id'),9 new HasPermission('manage-company', $value),10 ];11 }),12]);
Laravel Breeze API & Next.js
The Laravel Breeze API scaffolding and Next.js starter kit was contributed by Taylor Otwell and Miguel Piedrafita.
The Laravel Breeze starter kit has received an "API" scaffolding mode and complimentary Next.js frontend implementation. This starter kit scaffolding may be used to jump start your Laravel applications that are serving as a backend, Laravel Sanctum authenticated API for a JavaScript frontend.
Improved Ignition Exception Page
Ignition is developed by Spatie.
Ignition, the open source exception debug page created by Spatie, has been redesigned from the ground up. The new, improved Ignition ships with Laravel 9.x and includes light / dark themes, customizable "open in editor" functionality, and more.
Improved route:list
CLI Output
Improved route:list
CLI output was contributed by Nuno Maduro.
The route:list
CLI output has been significantly improved for the Laravel 9.x release, offering a beautiful new experience when exploring your route definitions.
Test Coverage Using Artisan test
Command
Test coverage when using the Artisan test
command was contributed by Nuno Maduro.
The Artisan test
command has received a new --coverage
option that you may use to explore the amount of code coverage your tests are providing to your application:
1php artisan test --coverage
1php artisan test --coverage
The test coverage results will be displayed directly within the CLI output.
In addition, if you would like to specify a minimum threshold that your test coverage percentage must meet, you may use the --min
option. The test suite will fail if the given minimum threshold is not met:
1php artisan test --coverage --min=80.3
1php artisan test --coverage --min=80.3
Soketi Echo Server
The Soketi Echo server was developed by Alex Renoki.
Although not exclusive to Laravel 9.x, Laravel has recently assisted with the documentation of Soketi, a Laravel Echo compatible Web Socket server written for Node.js. Soketi provides a great, open source alternative to Pusher and Ably for those applications that prefer to manage their own Web Socket server.
For more information on using Soketi, please consult the broadcasting documentation and Soketi documentation.
Improved Collections IDE Support
Improved collections IDE support was contributed by Nuno Maduro.
Laravel 9.x adds improved, "generic" style type definitions to the collections component, improving IDE and static analysis support. IDEs such as PHPStorm or static analysis tools such as PHPStan will now better understand Laravel collections natively.
New Helpers
Laravel 9.x introduces two new, convenient helper functions that you may use in your own application.
str
The str
function returns a new Illuminate\Support\Stringable
instance for the given string. This function is equivalent to the Str::of
method:
1$string = str('Taylor')->append(' Otwell');23// 'Taylor Otwell'
1$string = str('Taylor')->append(' Otwell');23// 'Taylor Otwell'
If no argument is provided to the str
function, the function returns an instance of Illuminate\Support\Str
:
1$snake = str()->snake('LaravelFramework');23// 'laravel_framework'
1$snake = str()->snake('LaravelFramework');23// 'laravel_framework'
to_route
The to_route
function generates a redirect HTTP response for a given named route, providing an expressive way to redirect to named routes from your routes and controllers:
1return to_route('users.show', ['user' => 1]);
1return to_route('users.show', ['user' => 1]);
If necessary, you may pass the HTTP status code that should be assigned to the redirect and any additional response headers as the third and fourth arguments to the to_route method:
1return to_route('users.show', ['user' => 1], 302, ['X-Framework' => 'Laravel']);
1return to_route('users.show', ['user' => 1], 302, ['X-Framework' => 'Laravel']);