> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# Upgrading from Laravel 9 to 10

> Upgrade steps and major changes for moving from Laravel 9.x to 10.x.

## Introduction

Laravel 10 was released on February 14, 2023. This guide walks through the upgrade from Laravel 9.x to 10.x.

<Info>
  Estimated time to upgrade is **about 10 minutes**. Actual impact from breaking changes depends on your app's size and features you use.
</Info>

### Automated upgrades with Laravel Shift

You can automate the upgrade with [Laravel Shift](https://laravelshift.com/). Shift updates dependencies and configuration files automatically.

***

## Changes by impact level

### Impact: high

* Dependency updates
* `minimum-stability` update

### Impact: medium

* Database expression changes
* Removal of the model `$dates` property
* Monolog 3
* Redis cache tags
* Service mocking changes
* The language directory

### Impact: low

* Closure validation rule messages
* The `after` method on form requests
* Public path binding
* `QueryException` constructor
* Rate limiter return value
* Removal of `Redirect::home`
* Removal of `Bus::dispatchNow`
* The `registerPolicies` method
* ULID columns

***

## Upgrade steps

### Update dependencies

**Impact: high**

Update the following dependencies in `composer.json`.

```json theme={null}
{
  "require": {
    "laravel/framework": "^10.0",
    "laravel/sanctum": "^3.2",
    "doctrine/dbal": "^3.0",
    "spatie/laravel-ignition": "^2.0"
  }
}
```

If you use these packages, update them too.

```json theme={null}
{
  "require": {
    "laravel/passport": "^11.0",
    "laravel/ui": "^4.0"
  }
}
```

If you're using PHPUnit 10, also update:

```json theme={null}
{
  "require-dev": {
    "nunomaduro/collision": "^7.0",
    "phpunit/phpunit": "^10.0"
  }
}
```

<Warning>
  With PHPUnit 10, remove the `processUncoveredFiles` attribute from the `<coverage>` section of `phpunit.xml`.
</Warning>

Then install dependencies:

```shell theme={null}
composer update
```

For Sanctum 2.x → 3.x, also consult the [Sanctum 3.x upgrade guide](https://github.com/laravel/sanctum/blob/3.x/UPGRADE.md).

***

### Update `minimum-stability`

**Impact: high**

Update `minimum-stability` in `composer.json` to `stable`. Since `stable` is the default, you can also remove this setting altogether.

```json theme={null}
"minimum-stability": "stable"
```

***

## PHP version requirement

**Impact: high**

Laravel 10 requires **PHP 8.1.0 or later** and **Composer 2.2.0 or later**.

***

## Breaking changes

### Application

#### Public path binding

**Impact: low**

If you customize the public path by binding `path.public` in the container, switch to `Illuminate\Foundation\Application`'s `usePublicPath` method.

```php theme={null}
app()->usePublicPath(__DIR__.'/public');
```

***

### Authorization

#### The `registerPolicies` method

**Impact: low**

The framework now calls `AuthServiceProvider`'s `registerPolicies` method automatically. You can remove the manual call from `boot`.

***

### Cache

#### Redis cache tags

**Impact: medium**

`Cache::tags()` is recommended only for apps using Memcached. If you use Redis as your cache driver, consider migrating to Memcached.

***

### Database

#### Database expression changes

**Impact: medium**

Database expressions produced by `DB::raw` have been rewritten. To get the raw string value of an expression, use the `getValue(Grammar $grammar)` method. Casting to `(string)` no longer works.

```php theme={null}
use Illuminate\Support\Facades\DB;

$expression = DB::raw('select 1');

// Before
$string = (string) $expression;

// After
$string = $expression->getValue(DB::connection()->getQueryGrammar());
```

Most end-user apps aren't affected, but if you cast database expressions to string, updates are required.

#### `QueryException` constructor

**Impact: very low**

The `Illuminate\Database\QueryException` constructor now takes a connection name string as the first argument. If you manually throw this exception, adjust the code.

#### ULID columns

**Impact: low**

When you call the `ulid` method in a migration without arguments, the column name is now `ulid`. In previous releases, calling it without arguments incorrectly created a column named `uuid`.

```php theme={null}
// The column name becomes "ulid"
$table->ulid();

// Specify a column name explicitly if needed
$table->ulid('ulid');
```

***

### Eloquent

#### The model `$dates` property

**Impact: medium**

The deprecated `$dates` property on Eloquent models has been removed. Use the `$casts` property instead.

```php theme={null}
// Before
protected $dates = ['deployed_at'];

// After
protected $casts = [
    'deployed_at' => 'datetime',
];
```

***

### Localization

#### The language directory

**Impact: none**

Existing apps aren't affected, but new Laravel application skeletons no longer include the `lang` directory by default. Publish it via an Artisan command if needed.

```shell theme={null}
php artisan lang:publish
```

***

### Logging

#### Monolog 3

**Impact: medium**

Laravel's Monolog dependency has been updated to Monolog 3.x. If you use Monolog directly in your app, review the [Monolog upgrade guide](https://github.com/Seldaek/monolog/blob/main/UPGRADE.md).

If you use third-party logging services like BugSnag or Rollbar, you may need to upgrade to versions that support both Monolog 3.x and Laravel 10.x.

<Tip>
  Monolog 3.x changes are documented in the [Monolog 3.x upgrade guide](https://github.com/Seldaek/monolog/blob/3.x/UPGRADE.md).
</Tip>

***

### Queue

#### Removal of `Bus::dispatchNow`

**Impact: low**

The deprecated `Bus::dispatchNow` and `dispatch_now` methods have been removed. Use `Bus::dispatchSync` and `dispatch_sync`.

```php theme={null}
// Before
Bus::dispatchNow(new MyJob());
dispatch_now(new MyJob());

// After
Bus::dispatchSync(new MyJob());
dispatch_sync(new MyJob());
```

***

### Routing

#### Middleware aliases

**Impact: optional**

In new Laravel apps, `App\Http\Kernel`'s `$routeMiddleware` property was renamed to `$middlewareAliases`. Applying it to existing apps is optional.

#### Rate limiter return value

**Impact: low**

When you call `RateLimiter::attempt`, the return value of the provided closure is now returned as-is. If you return nothing or `null`, `true` is returned.

```php theme={null}
$value = RateLimiter::attempt('key', 10, fn () => ['example'], 1);

$value; // ['example']
```

#### Removal of `Redirect::home`

**Impact: very low**

The deprecated `Redirect::home` method has been removed. Redirect to a named route explicitly.

```php theme={null}
// Before
return Redirect::home();

// After
return Redirect::route('home');
```

***

### Testing

#### Service mocking

**Impact: medium**

The deprecated `MocksApplicationServices` trait has been removed from the framework. It provided test methods like `expectsEvents`, `expectsJobs`, and `expectsNotifications`.

If you use these methods, migrate to `Event::fake`, `Bus::fake`, and `Notification::fake` respectively.

```php theme={null}
// Before
$this->expectsEvents(OrderShipped::class);

// After
Event::fake();
// ... test code ...
Event::assertDispatched(OrderShipped::class);
```

***

### Validation

#### Closure validation rule messages

**Impact: very low**

In closure-based custom validation rules, calling `$fail` multiple times now appends messages to an array instead of overwriting.

Also, the `$fail` callback now returns an object. If you type-hint the return value of a validation closure, update it.

```php theme={null}
public function rules()
{
    return [
        'name' => [
            function ($attribute, $value, $fail) {
                $fail('validation.translation.key')->translate();
            },
        ],
    ];
}
```

#### The `after` method on form requests

**Impact: very low**

Laravel now reserves an `after` method on form requests. If your form request defines an `after` method, rename it or migrate to the new "after validation" feature.

***

## Summary

Laravel 10 involves relatively small changes, and the upgrade can be completed quickly.

| Change                              | Impact   | Action                                    |
| ----------------------------------- | -------- | ----------------------------------------- |
| Update `composer.json` dependencies | High     | Set `laravel/framework ^10.0`             |
| PHP 8.1 / Composer 2.2 required     | High     | Verify and update versions                |
| Database expression changes         | Medium   | Replace `(string)` cast with `getValue()` |
| Model `$dates` property removed     | Medium   | Migrate to `$casts`                       |
| Monolog 3                           | Medium   | Check the upgrade guide if used directly  |
| Redis cache tags                    | Medium   | Consider migrating to Memcached           |
| `MocksApplicationServices` removed  | Medium   | Migrate to `Event::fake` and friends      |
| `Bus::dispatchNow` removed          | Low      | Use `Bus::dispatchSync`                   |
| ULID column name                    | Low      | Review `ulid()` calls in migrations       |
| `Redirect::home` removed            | Very low | Use `Redirect::route('home')`             |

***

## References

* [Official upgrade guide (English)](https://laravel.com/docs/10.x/upgrade)
* [laravel/laravel diff (9.x → 10.x)](https://github.com/laravel/laravel/compare/9.x...10.x)
* [Laravel Shift](https://laravelshift.com) — community service to automate upgrades
* [Sanctum upgrade guide (2.x → 3.x)](https://github.com/laravel/sanctum/blob/3.x/UPGRADE.md)
* [Monolog 3.x upgrade guide](https://github.com/Seldaek/monolog/blob/3.x/UPGRADE.md)


## Related topics

- [Upgrading from Laravel 10 to 11](/en/blog/upgrade-10-to-11.md)
- [Upgrading from Laravel 8 to 9](/en/blog/upgrade-8-to-9.md)
- [Application Structure in Laravel 11 and Later](/en/advanced/app-structure.md)
- [Upgrade guide: Laravel 12 to 13](/en/blog/upgrade-12-to-13.md)
- [Laravel Horizon](/en/horizon.md)
