Laravel 13.33 Released: Key Features and Improvements
The Laravel team shipped v13.33.0 on September 23, 2026, with a solid set of developer-focused additions. Here is a breakdown of the most impactful changes.
Tagged Memoized Cache
Contributed by Joost de Bruijn, Cache::memo() now supports tagged caches via a tags() method. Within a single request, repeated reads for the same key hit the cache backend only once — subsequent reads are served from memory.
// Two calls, one round trip to Redis
Cache::memo()->tags(['permissions'])->get("permissions:{$user->id}");
Cache::memo()->tags(['permissions'])->get("permissions:{$user->id}");
// Writes go to the tagged cache and drop the memoized copy
Cache::memo()->tags(['permissions'])->put("permissions:{$user->id}", $permissions);
// Flushing the tag also clears memoized entries
Cache::memo()->tags(['permissions'])->flush();
Before this change, combining tagged caches with memoization required a manual Cache::store('array') wrapper. See #61593.
Refresh Model Attributes After Writes
Caleb White contributed a #[Refreshes] PHP attribute (and a $refreshes property alternative) that reloads specific database columns after a model is created or updated. This is particularly useful for virtualAs and storedAs generated columns that the database computes at write time.
use Illuminate\Database\Eloquent\Attributes\Refreshes;
#[Refreshes('name')]
class User extends Model
{
//
}
$user = User::create(['first_name' => 'Taylor', 'last_name' => 'Otwell']);
$user->name; // "Taylor Otwell" — no manual refresh() call needed
The refresh issues one targeted query for the listed columns only, on the write connection. See #61523.
Store a Real NULL With AsCollection and AsArrayObject
Zein Ahmad added an opt-in nullable() mode to both casts. Without it, assigning null stores the JSON string "null", which breaks whereNull() queries. With nullable(), the column stores a proper database NULL.
protected function casts(): array
{
return [
'items' => AsCollection::class, // stores "null"
'meta' => AsCollection::nullable(), // stores NULL
'options' => AsArrayObject::nullable(),
];
}
See #61596.
inplace() and lock() for Index Migrations
Sander Muller added inplace() and lock() modifiers for index and foreign key operations, letting MySQL migrations request ALGORITHM=INPLACE and LOCK=NONE to avoid blocking table rebuilds.
Schema::table('video_sessions', function (Blueprint $table) {
$table->index('foo', 'foo_index')->inplace()->lock('none');
});
Supported methods: index(), unique(), primary(), fullText(), spatialIndex(), and foreign(). See #61602.
Other Notable Changes
- Form Request parent attributes —
#[StopOnFirstFailure]and similar attributes on a baseFormRequestclass are now inherited by child requests (#61586). - Worker::$killOnTimeout — Set to
falseto throwTimeoutExceededExceptioninstead of killing the worker process on job timeout (#61591). - mockArtisan() / realArtisan() — New test helpers that cleanly separate expectation-based Artisan mocking from real command execution (#61588).
- Valkey protocol support —
valkey://andvalkeys://connection strings are now recognized (#61635).
Key Takeaways
Cache::memo()->tags([...])eliminates redundant cache backend round trips for tagged keys within a request.#[Refreshes]keeps generated/computed columns accurate on the in-memory model after every write.AsCollection::nullable()ensureswhereNull()works correctly on JSON cast columns.inplace()on index migrations reduces downtime risk on large MySQL tables.mockArtisan()andrealArtisan()make Artisan testing intent explicit without toggling$mockConsoleOutput.
Source: Laravel News — Laravel 13.33.0