psalm/plugin-laravel
Laravel Psalm plugin for deep static analysis plus taint-based security scanning. Detects SQL injection, XSS, SSRF, shell injection, path traversal, and open redirects by tracking user input through Laravel code without running it.
This release improves Eloquent pluck() and builder type narrowing, plus primary-key and factory binding inference.
pluck()->all() to list<TValue> (#1299) Task::all()->pluck('id')->all();
-// array<int, int> -- rejected by a list<int> return type
+// list<int> -- pluck() always reindexes sequentially
pluck() and aggregate narrowing on custom subclasses, raw aliases, and schema-typed columns (#1288) /** [@extends](https://github.com/extends) Builder<Task> */
class TaskBuilder extends Builder {}
$tasks->pluck('title');
-// Collection<array-key, mixed> -- LHS fallback only checked generic builders
+// Collection<int, string> -- resolved via [@extends](https://github.com/extends) Builder<Task>
Factory<TModel> on bare factory subclasses (#1280) class TaskFactory extends Factory {}
(new TaskFactory())->create();
-// Model -- TModel collapses to base Model with no [@extends](https://github.com/extends) Factory<Task> binding
+// Task -- model resolved via Factory::modelName() at warm-up
Model::getKey() to the model's known primary-key type (#1279) class UuidModel extends Model { use HasUuids; }
$model->getKey();
-// int|string -- always the stub's fallback
+// string -- narrowed from the model's known primary-key type
// Order has a custom builder with `firstByMake(): ?self`
$vehicle->orders()->firstByMake('Toyota');
-// mixed -- custom builder-only methods weren't resolved through the relation
+// ?Order -- forwarded through the relation via Psalm storage
Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v4.15.0...v4.15.1
This release improves Eloquent pluck() and builder type narrowing, plus primary-key and factory binding inference.
pluck()->all() to list<TValue> (#1299) Task::all()->pluck('id')->all();
-// array<int, int> -- rejected by a list<int> return type
+// list<int> -- pluck() always reindexes sequentially
pluck() and aggregate narrowing on custom subclasses, raw aliases, and schema-typed columns (#1288) /** [@extends](https://github.com/extends) Builder<Task> */
class TaskBuilder extends Builder {}
$tasks->pluck('title');
-// Collection<array-key, mixed> -- LHS fallback only checked generic builders
+// Collection<int, string> -- resolved via [@extends](https://github.com/extends) Builder<Task>
Factory<TModel> on bare factory subclasses (#1280) class TaskFactory extends Factory {}
(new TaskFactory())->create();
-// Model -- TModel collapses to base Model with no [@extends](https://github.com/extends) Factory<Task> binding
+// Task -- model resolved via Factory::modelName() at warm-up
Model::getKey() to the model's known primary-key type (#1279) class UuidModel extends Model { use HasUuids; }
$model->getKey();
-// int|string -- always the stub's fallback
+// string -- narrowed from the model's known primary-key type
// Order has a custom builder with `firstByMake(): ?self`
$vehicle->orders()->firstByMake('Toyota');
-// mixed -- custom builder-only methods weren't resolved through the relation
+// ?Order -- forwarded through the relation via Psalm storage
Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v3.15.0...v3.15.1
v3.15.0 ships the biggest architectural change in the plugin's history: ModelMetadataRegistry, a single per-model metadata store that replaces the ad-hoc reflection and private caches every Eloquent handler used to maintain on its own. This release builds three new model-correctness diagnostics on top of it, validating attributes and relationships on your models, plus a more precise serialization inference — and it's the foundation a lot more diagnostics are planned to build on next.
UnknownModelAttribute and UndefinedModelRelation report at info by default. Opt into error with <experimental value="true" />.UnresolvableAppendedModelAttribute reports at error by default: a $appends entry with no matching accessor or cast now fails analysis. Downgrade it with a standard issueHandlers entry if that doesn't fit your codebase yet.attributesToArray() / toArray() now infer a precise array shape. Always active, no config needed.ModelMetadataRegistry, a per-model metadata store warmed once during AfterCodebasePopulated and shared read-only across every Eloquent handler (#1081, #1201)experimental config flag to promote experimental diagnostics to error (#1248)UnknownModelAttribute: flags a create()/fill()/update() key with no backing column, cast, accessor, $appends entry, or [@property](https://github.com/property) (#1167) Model::create(['nmae' => 'value']);
-// silently drops the typo, sets nothing
+// UnknownModelAttribute: 'nmae' matches no column, cast, accessor, $appends entry, or [@property](https://github.com/property)
UnresolvableAppendedModelAttribute: flags a $appends entry with no accessor or cast to actually produce it (#1169)UndefinedModelRelation: validates relation names passed to with()/load()/has()/whereHas() against the resolved model (#1181) Customer::with('vehicles.typoOnVehicle');
-// no warning, silently ignored at runtime
+// UndefinedModelRelation: Relation 'typoOnVehicle' is not defined on App\Models\Vehicle
attributesToArray()/toArray(), honoring casts, hidden, and appended attributes (#1168, #1184)UnknownModelAttribute (#1265)RelationResolver from autoloading related models, fixing an analysis crash (#1272)Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v3.14.1592...v3.15.0
v4.15.0 ships the 2nd biggest architectural change in the plugin's history: ModelMetadataRegistry, a single per-model metadata store that replaces the ad-hoc reflection and private caches every Eloquent handler used to maintain on its own. This release builds three new model-correctness diagnostics on top of it, validating attributes and relationships on your models, plus a more precise serialization inference — and it's the foundation a lot more diagnostics are planned to build on next.
UnknownModelAttribute and UndefinedModelRelation report at info by default. Opt into error with <experimental value="true" />.UnresolvableAppendedModelAttribute reports at error by default: a $appends entry with no matching accessor or cast now fails analysis. Downgrade it with a standard issueHandlers entry if that doesn't fit your codebase yet.attributesToArray() / toArray() now infer a precise array shape. Always active, no config needed.ModelMetadataRegistry, a per-model metadata store warmed once during AfterCodebasePopulated and shared read-only across every Eloquent handler (#1081, #1201)experimental psalm.xml config flag to promote experimental features for early adopters (#1248)UnknownModelAttribute: flags a create()/fill()/update() key with no backing column, cast, accessor, $appends entry, or [@property](https://github.com/property) (#1167) Model::create(['nmae' => 'value']);
-// silently drops the typo, sets nothing
+// UnknownModelAttribute: 'nmae' matches no column, cast, accessor, $appends entry, or [@property](https://github.com/property)
UnresolvableAppendedModelAttribute: flags a $appends entry with no accessor or cast to actually produce it (#1169)UndefinedModelRelation: validates relation names passed to with()/load()/has()/whereHas() against the resolved model (#1181) Customer::with('vehicles.typoOnVehicle');
-// no warning, silently ignored at runtime
+// UndefinedModelRelation: Relation 'typoOnVehicle' is not defined on App\Models\Vehicle
attributesToArray()/toArray(), honoring casts, hidden, and appended attributes (#1168, #1184)whereDate two-argument overload (#1247)Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v4.14.12...v4.15.0
3.14159265... close enough. Here's your π release, the last one, before a big v3.15
Eloquent\Builder::whereDate() rejecting DateTimeInterface values (Carbon, DateTimeImmutable) in the two-argument form (#1247) Post::query()->whereDate('created_at', CarbonImmutable::now());
-// ImplicitToStringCast (before): Argument 2 expects string but Carbon provided
+// now resolves: whereDate() accepts DateTimeInterface in two-arg form
Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v3.14.12...v3.14.1592
Narrow several producer and validation return types to their concrete implementations, plus fixes for Cache::driver()->flexible() and monorepo init scanning.
Password::broker(), view()/trans(), and query builder pagination results to their concrete Laravel implementation (#1240) Password::broker()->createToken($user);
-// UndefinedInterfaceMethod (before): createToken() only exists on the concrete broker
+// now resolves: typed as Auth\Passwords\PasswordBroker
view('welcome')->fragment('x');
-// UndefinedInterfaceMethod (before): fragment() only exists on Illuminate\View\View
+// now resolves: typed as View\View
integer()/boolean() accessors and rule-bound integer fields from validation rules (#1239, #1237)UndefinedInterfaceMethod false positive on Cache::driver()->flexible() (#1233)<projectFiles> in psalm-laravel init on Composer monorepos (#1235)Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v3.14.11...v3.14.12
Narrow several producer and validation return types to their concrete implementations, plus fixes for Cache::driver()->flexible() and monorepo init scanning.
Password::broker(), view()/trans(), and query builder pagination results to their concrete Laravel implementation (#1240) Password::broker()->createToken($user);
-// UndefinedInterfaceMethod (before): createToken() only exists on the concrete broker
+// now resolves: typed as Auth\Passwords\PasswordBroker
view('welcome')->fragment('x');
-// UndefinedInterfaceMethod (before): fragment() only exists on Illuminate\View\View
+// now resolves: typed as View\View
integer()/boolean() accessors and rule-bound integer fields from validation rules (#1239, #1237)UndefinedInterfaceMethod false positive on Cache::driver()->flexible() (#1233)<projectFiles> in psalm-laravel init on Composer monorepos (#1235)Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v4.14.11...v4.14.12
Collection type inference for arbitrary inputs, and partial Laravel boots now surface instead of failing silently, plus three false-positive fixes around query builders and monorepo init already shipped on the 4.x line.
This release bumps min supported Laravel minor versions:
collect(), Collection::make(), and LazyCollection::make() return types for arbitrary inputs, not just arrays (#1225) collect('hello');
-// InvalidArgument (before): string wasn't Arrayable|iterable|null
+// now infers: Collection<0, 'hello'>
collect(BackedSuit::Hearts);
-// InvalidArgument (before): enum cases weren't accepted either
+// now infers: Collection<0, BackedSuit>
collect(null);
-// before: Collection<array-key, mixed> (unbound fallback)
+// now infers: Collection<never, never>
UndefinedMagicMethod on custom Eloquent builder subclass methods called after a fluent chain (#1219)UndefinedMagicMethod on IndexDefinition modifiers in database migrations (#1220)psalm-laravel init on monorepos: no longer puts packages/ into <ignoreFiles>, which silently disabled analysis of package sources (#1214)bootstrap() failures: a partial Laravel boot now emits a warning by default, and fails the run under failOnInternalError (#1226)Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v3.14.10...v3.14.11
Collection type inference for arbitrary inputs, and partial Laravel boots now surface instead of failing silently.
This release bumps min supported Laravel minor versions:
collect(), Collection::make(), and LazyCollection::make() return types for arbitrary inputs, not just arrays (#1225) collect('hello');
-// InvalidArgument (before): string wasn't Arrayable|iterable|null
+// now infers: Collection<0, 'hello'>
collect(BackedSuit::Hearts);
-// InvalidArgument (before): enum cases weren't accepted either
+// now infers: Collection<0, BackedSuit>
collect(null);
-// before: Collection<array-key, mixed> (unbound fallback)
+// now infers: Collection<never, never>
bootstrap() failures: a partial Laravel boot now emits a warning by default, and fails the run under failOnInternalError (#1226)Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v4.14.10...v4.14.11
A patch release that focused on removing some false positives.
TaintedSql on where() calls that pass a column-to-value map (where(['name' => $value])) (#1221)Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v3.14.9...v3.14.10
A patch release that removes three false positives around query builders and taint analysis, and fixes a psalm-laravel init misconfiguration on monorepos.
TaintedSql on where() calls that pass a column-to-value map (where(['name' => $value])) (#1221)UndefinedMagicMethod on custom Eloquent builder subclass methods called after a fluent chain (#1219)UndefinedMagicMethod on IndexDefinition modifiers in database migrations (#1220)psalm-laravel init on monorepos: no longer puts packages/ into <ignoreFiles>, which silently disabled analysis of package sources (#1214)Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v4.14.9...v4.14.10
This release improves custom Eloquent builder inference and narrows several stub return types.
newQuery(), newQueryWithoutScopes(), etc. (#1207)getCasts() and getTouchedRelations() return types (#1205)Request::route()/Request::file() default value handling (#1205)Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v3.14.8...v3.14.9
This release improves custom Eloquent builder inference and narrows several stub return types.
newQuery(), newQueryWithoutScopes(), etc. (#1207)getCasts() and getTouchedRelations() return types (#1205)Request::route()/Request::file() default value handling (#1205)Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v4.14.8...v4.14.9
Patch release fixing Psalm execution on Windows.
proc_open(): CreateProcess failed warning on Windows by executing psalm through the current PHP binary instead of relying on the shebang (#1189) @HenkPoleyFull Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v3.14.7...v3.14.8
Patch release fixing Psalm execution on Windows.
proc_open(): CreateProcess failed warning on Windows by executing psalm through the current PHP binary instead of relying on the shebang (#1189) @HenkPoleyFull Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v4.14.7...v4.14.8
Fixes caching issue, that makes Psalm complaining about CastsAttributes.
CastsAttributes: restore user_defined on cast contracts on warm cache (#1188)Arr/Lottery stub shells being removed, which caused UndefinedClass in projects that don't reflect illuminate/support (e.g. laravel/socialite)Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v3.14.6...v3.14.7
Fixes caching issue, that makes Psalm complaining about CastsAttributes.
CastsAttributes: Restore user_defined on cast contracts on warm cache (#1188)Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v4.14.6...v4.14.7
sum/avg/min/max return types on relations and collections instead of widening to mixed (#1183)UndefinedMethod on forwarded Query\Builder methods in custom builder subclasses (#1145)migrator resolution when the app bootstraps partially, avoiding a crash during analysis (#1175)Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v3.14.5...v3.14.6
sum/avg/min/max return types on relations and collections instead of widening to mixed (#1183)UndefinedMethod on forwarded Query\Builder methods in custom builder subclasses (#1145)migrator resolution when the app bootstraps partially, avoiding a crash during analysis (#1175)Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v4.14.5...v4.14.6
This patch sharpens conditional return types for several Laravel helpers and guards against two crash paths during partial app bootstrap.
Pagination::fragment(), Arr::random(), and Lottery::choose() (#1176)Route::domain() so it narrows $this vs string (#1174)Paginator::through() to the updated template after the callback (#1173)migrator resolution so analysis no longer crashes when the app bootstraps partially (#1175)psalm-laravel analyze through to psalm CLI (#1158)Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v3.14.4...v3.14.5
This patch sharpens conditional return types for several Laravel helpers and guards against two crash paths during partial app bootstrap.
Pagination::fragment(), Arr::random(), and Lottery::choose() (#1176)Route::domain() so it narrows $this vs string (#1174)Paginator::through() to the updated template after the callback (#1173)migrator resolution so analysis no longer crashes when the app bootstraps partially (#1175)psalm-laravel analyze through to psalm CLI (#1158)Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v4.14.4...v4.14.5
This patch sharpens type inference across collections, the console, and the Date facade, and improves Laravel 11 support by handling Carbon 2.
Command::hasArgument()/hasOption() to literal bool, and fix hasOption() shortcut/negation semantics (#1161)Date facade static calls (#1157)literal() helper (#1160)config()->collection() to Collection<key, value> (#1159)random() return type on Enumerable and LazyCollection (#1152)null option shortcut in Command::getOptions() (#1166)Model::loadCount() constraint closure as Builder (#1164)null values in the __() $replace parameter (#1151)SessionGuard::hashPasswordForCookie() on Laravel 11 (#1146)psalm-plugin init from silently disabling type analysis on Psalm 6 (#1144)Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v3.14.2...v3.14.4
This patch sharpens type inference across collections, the console, and the Date facade.
Command::hasArgument()/hasOption() to literal bool, and fix hasOption() shortcut/negation semantics (#1161)Date facade static calls (#1157)literal() helper (#1160)config()->collection() to Collection<key, value> (#1159)random() return type on Enumerable and LazyCollection (#1152)null option shortcut in Command::getOptions() (#1166)Model::loadCount() constraint closure as Builder (#1164)null values in the __() $replace parameter (#1151)SessionGuard::hashPasswordForCookie() on Laravel 11 (#1146)Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v4.14.2...v4.14.4
Patch release: fewer false positives on Laravel's Application contract and uploaded files, plus a simpler (but more powerful and secure) generated CI workflow added via psalm-laravel add ci.
Application methods (isProduction(), isLocal(), path(), ...) when the receiver is typed on the Contracts\Foundation\Application interface, eliminating false UndefinedInterfaceMethod on app(), ServiceProvider::$app, and Command::$laravel (#1141)TaintedFile / TaintedSSRF on UploadedFile reads: its only string coercion is the server-controlled temp path, while the user-controlled accessors (contents, client name, MIME type) stay tainted (#1136)Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v3.14.1...v3.14.2
Patch release: fewer false positives on Laravel's Application contract and uploaded files, plus a simpler (but more powerful and secure) generated CI workflow added via psalm-laravel add ci.
Application methods (isProduction(), isLocal(), path(), ...) when the receiver is typed on the Contracts\Foundation\Application interface, eliminating false UndefinedInterfaceMethod on app(), ServiceProvider::$app, and Command::$laravel (#1141)TaintedFile / TaintedSSRF on UploadedFile reads: its only string coercion is the server-controlled temp path, while the user-controlled accessors (contents, client name, MIME type) stay tainted (#1136)Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v4.14.1...v4.14.2
Patch release for the 3.x (Psalm 6) line. Fixes the init command emitting Psalm 7-only configuration, plus Laravel 11 analysis correctness plus other enhancements and fixes from 4.x branch:
$this in Artisan::command() closures so command callbacks resolve $this to the command instance (#1122)Artisan::command() $this override to the callback closure so it no longer leaks to surrounding scope (#1127)UndefinedMethod on app('encrypter') method calls (#1128)UndefinedMethod on guard methods reached via auth() narrowing (#1121)tap() form as HigherOrderTapProxy<T> (#1112)orderBy() direction ('ASC'/'DESC') on Laravel <13.8 (#1111)Query\Builder::orderBy() direction as asc/desc literals plus SortDirection (#1104)Expression as the $sql argument to Query\Builder::whereRaw() (#1106)Filesystem::hash() return as non-empty-string|false (#1109)find() (#1105)firstOrNew/updateOrCreate closure values on Laravel 13.5+ (#1102)Connection::cursor() rows as stdClass instead of mixed (#1100)Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v3.14.0...v3.14.1
Patch release focused on Eloquent and query builder type accuracy, plus follow-up fixes to console closures and facade method resolution.
$this in Artisan::command() closures so command callbacks resolve $this to the command instance (#1122 #1127)UndefinedMethod on app('encrypter') method calls (#1128)UndefinedMethod on guard methods reached via auth() narrowing (#1121)tap() form as HigherOrderTapProxy<T> (#1112)orderBy() direction ('ASC'/'DESC') on Laravel <13.8 (#1111)Query\Builder::orderBy() direction as asc/desc literals plus SortDirection (#1104)Expression as the $sql argument to Query\Builder::whereRaw() (#1106)Filesystem::hash() return as non-empty-string|false (#1109)find() (#1105)firstOrNew/updateOrCreate closure values on Laravel 13.5+ (#1102)Connection::cursor() rows as stdClass instead of mixed (#1100)Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v4.14.0...v4.14.1
v3.14.0 spans Eloquent type inference and security: three new security detectors, relationship and filesystem narrowing, paginator fixes, and an opt-in query-builder best-practice rule.
html_url taint kind for URL-context XSS distinct from e() (#1014)ImplicitQueryBuilderCall rule for direct model query and scope calls. It requires writing User::query()->where(...) instead of User::where(...) (#1068)MissingTemplateParam on in-body belongsToMany()/morphToMany() pivot chains (#1090)mixed collapse when chaining off relation methods, by replacing $this with static in generic templates and using [@template-covariant](https://github.com/template-covariant) for relationships (#1055)Storage::disk() to FilesystemAdapter so temporaryUrl() and url() resolve (#1093)FilesystemAdapter listing methods to list<string> (#1094)FormRequest magic property reads ($request->field calls) (#1022)app(Foo::class) as Foo when the booted app cannot resolve it (Laravel packages) (#1076)UndefinedMethod on $app->when()->needs() chains (#1077)when()/unless() callback return types (#997)paginate() and preserve the related-model template through relations (#1082)PossiblyUnusedMethod on Eloquent trait boot/initialize hooks (#1080)/psalm-delta workflow to benchmark plugin over real apps and libs (#1079, #1086, #1084) @alies-devFull Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v3.13.2...v3.14.0
v4.14.0 spans Eloquent type inference and security: three new security detectors, relationship and filesystem narrowing, paginator fixes, and an opt-in query-builder best-practice rule.
html_url taint kind for URL-context XSS distinct from e() (#1014)ImplicitQueryBuilderCall rule for direct model query and scope calls. It requires writing User::query()->where(...) instead of User::where(...) (#1068)MissingTemplateParam on in-body belongsToMany()/morphToMany() pivot chains (#1090)mixed collapse when chaining off relation methods, by replacing $this with static in generic templates and using [@template-covariant](https://github.com/template-covariant) for relationships (#1055)Storage::disk() to FilesystemAdapter so temporaryUrl() and url() resolve (#1093)FilesystemAdapter listing methods to list<string> (#1094)FormRequest magic property reads ($request->field calls) (#1022)app(Foo::class) as Foo when the booted app cannot resolve it (Laravel packages) (#1076)UndefinedMethod on $app->when()->needs() chains (#1077)when()/unless() callback return types (#997)paginate() and preserve the related-model template through relations (#1082)PossiblyUnusedMethod on Eloquent trait boot/initialize hooks (#1080)^12.4 (#1089)/psalm-delta workflow to benchmark the plugin over real apps and libraries (#1079, #1086, #1084)Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v4.13.2...v4.14.0
PublicModelScope and PublicModelAccessor rules for public model scopes and accessors (#1056) @alies-devScopes and Eloquent:
Builder<Model> from $class::query() (#1073) @alies-dev->orWhere->scope()) (#1066) @alies-devOther:
App::make() facade calls to the resolved class (#1072) @alies-devFull Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v3.13.1...v3.13.2
PublicModelScope and PublicModelAccessor rules for public model scopes and accessors (#1056) @alies-devScopes and Eloquent:
Builder<Model> from $class::query() (#1073) @alies-dev->orWhere->scope()) (#1066) @alies-devOther:
App::make() facade calls to the resolved class (#1072) @alies-devFull Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v4.13.1...v4.13.2
InvalidArgument on addGlobalScope closures with bare Builder params by @alies-dev in https://github.com/psalm/psalm-plugin-laravel/pull/1045#[Scope] detection and scope-vs-QueryBuilder params precedence by @alies-dev in https://github.com/psalm/psalm-plugin-laravel/pull/1046Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v3.13.0...v3.13.1
#[Scope] detection and scope-vs-QueryBuilder params precedence (#1046) @alies-devInvalidArgument on addGlobalScope closures with bare Builder params (#1045) @alies-devFull Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v4.13.0...v4.13.1
A comprehensive overhaul of Eloquent query-scope analysis: instance scope calls on the base Builder, self/static scope parameters, direct-vs-forwarded dispatch, and variadic / name-collision handling.
Builder to Builder<Model> instead of mixed, type-checking arguments against the scope's declared params (#1032)self/static scope parameters to the model on custom builders, removing false InvalidArgument and Model&static over-narrowing (#1033)?Builder, variadic, and non-variable first arguments resolve correctly (#1041)Eloquent\Builder method such as find() (#1042)$query explicitly to keep the real method signature, with no left-shifted arguments (#1035)$query to use the real return type instead of a fabricated Builder<Model> (#1036)self parameters to resolve to the composing class, so a sibling subclass is accepted instead of rejected with a false InvalidArgument (#1043)InvalidArgument message for a bad scope argument now names the composing parent (e.g. AbstractDocument) instead of the queried child (e.g. Contract) (#1031, #1043). No migration needed; the diagnostic is simply more accurate.Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v3.12.3...v3.13.0
A comprehensive overhaul of Eloquent query-scope analysis: instance scope calls on the base Builder, self/static scope parameters, direct-vs-forwarded dispatch, and variadic / name-collision handling.
Builder to Builder<Model> instead of mixed, type-checking arguments against the scope's declared params (#1032)self/static scope parameters to the model on custom builders, removing false InvalidArgument and Model&static over-narrowing (#1033)?Builder, variadic, and non-variable first arguments resolve correctly (#1041)Eloquent\Builder method such as find() (#1042)$query explicitly to keep the real method signature, with no left-shifted arguments (#1035)$query to use the real return type instead of a fabricated Builder<Model> (#1036)self parameters to resolve to the composing class, so a sibling subclass is accepted instead of rejected with a false InvalidArgument (#1043)InvalidArgument message for a bad scope argument now names the composing parent (e.g. AbstractDocument) instead of the queried child (e.g. Contract) (#1031, #1043). No migration needed; the diagnostic is simply more accurate.Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v4.12.4...v4.13.0
The same as v4.12.3 (that was initially tagged on a wrong branch)
md5() / sha1() (CWE-328) by @alies-dev in https://github.com/psalm/psalm-plugin-laravel/pull/1012Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v4.12.2...v4.12.4
⚠️ This version was wrongly tagged on 3.x branch, please ignore it.
Backported features from v4.12.1 and v4.12.2
$this->input() return type inside FormRequest (#1017) @alies-devCastsAttributes / Castable casts to real types (#1020) @alies-devCollection::where() return types (#1019) @alies-devFull Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v3.12.0...v3.12.3
CastsAttributes / Castable casts to real types (#1020) @alies-devCollection::where() return types (#1019) @alies-devFull Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v4.12.1...v4.12.2
$this->input() return type inside FormRequest by @alies-dev in https://github.com/psalm/psalm-plugin-laravel/pull/1017Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v4.12.0...v4.12.1
Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v2.12.2...v2.12.3
config() and Repository::get() return types (#1006) @alies-devJs::from() and Js::encode() taint per call-site (reduce false-positive reports) (#1010) @alies-devRequest::query() and Request::post() return types (#1009) @alies-devFull Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v3.11.0...v3.12.0
config() and Repository::get() return types (#1006) — opt-out by a new resolveConfigReturnTypes config key @alies-devJs::from() and Js::encode() taint per call-site (reduce false-positive reports) (#1010) @alies-devRequest::query() and Request::post() return types (#1009) @alies-devFull Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v4.11.0...v4.12.0
where{Column} on Model direct calls (#1001) @alies-devBuilder aggregate (avg(), max(), sum(), etc) returns using a known column type (#1005) @alies-devwhere{Column} arguments on Eloquent relations (#939) @alies-devwhere{Column} method calls (#980) @alies-devpluck($value, $key) key type and cover relation chains (#968) @alies-devModel::only() return shape from literal keys (#933) @alies-devSET columns to literal union in ModelPropertyHandler (#932) @alies-devMacroable return type info from docblock Psalm storage (#989) @alies-dev: static return types (#987) @alies-devdiagnose subcommand for runtime introspection (#959) @alies-devdiagnose command and enhance config created by the init command (#971) @alies-devpsalm-laravel init command @alies-devModel::factory()->create() collapse on bare HasFactory (#964) @alies-devauth($name) return to concrete guard class (#981) @alies-devStorage::disk() return to Cloud for cloud-driver disks (#982) @alies-devRoute::middleware facade and RouteRegistrar (#986) @alies-devRequest::file() via source-level conditional return (#935) @alies-devPossiblyUnusedMethod):
PossiblyUnusedMethod for legacy scopeXxx() Eloquent methods (#999) @alies-devPossiblyUnusedMethod for #[Scope]-attributed Eloquent methods (#998) @alies-devconfig_path() at the project root under Testbench fallback (#949) @alies-devFull Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v3.10.2...v3.11.0
where{Column} on Model direct calls (#1001) @alies-devBuilder aggregate (avg(), max(), sum(), etc) returns using a known column type (#1005) @alies-devwhere{Column} arguments on Eloquent relations (#939) @alies-devwhere{Column} method calls (#980) @alies-devpluck($value, $key) key type and cover relation chains (#968) @alies-devModel::only() return shape from literal keys (#933) @alies-devSET columns to literal union in ModelPropertyHandler (#932) @alies-devMacroable return type info from docblock Psalm storage (#989) @alies-dev: static return types (#987) @alies-devdiagnose subcommand for runtime introspection (#959) @alies-devdiagnose command and enhance config created by the init command (#971) @alies-devpsalm-laravel init command @alies-devModel::factory()->create() collapse on bare HasFactory (#964) @alies-devauth($name) return to concrete guard class (#981) @alies-devStorage::disk() return to Cloud for cloud-driver disks (#982) @alies-devRoute::middleware facade and RouteRegistrar (#986) @alies-devRequest::file() via source-level conditional return (#935) @alies-devPossiblyUnusedMethod):
PossiblyUnusedMethod for legacy scopeXxx() Eloquent methods (#999) @alies-devPossiblyUnusedMethod for #[Scope]-attributed Eloquent methods (#998) @alies-devconfig_path() at the project root under Testbench fallback (#949) @alies-devFull Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v4.10.2...v4.11.0
list<T> from Collection::values()->all() chain by @alies-dev in https://github.com/psalm/psalm-plugin-laravel/pull/921Rules\Enum and Rules\In object forms in validated() by @alies-dev in https://github.com/psalm/psalm-plugin-laravel/pull/911PropertyNotSetInConstructor on Laravel testing lifecycle properties by @alies-dev in https://github.com/psalm/psalm-plugin-laravel/pull/916Factory::hasAttached rejects Eloquent\Collection from count()->create() by @alies-dev in https://github.com/psalm/psalm-plugin-laravel/pull/915Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v3.10.1...v3.10.2
Tighter Request::validated() narrowing, sharper Collection chain inference, and two false-positive fixes in testing + factory flows.
list<T> from Collection::values()->all() chain by @alies-dev in https://github.com/psalm/psalm-plugin-laravel/pull/921Rules\Enum and Rules\In object forms in validated() by @alies-dev in https://github.com/psalm/psalm-plugin-laravel/pull/911PropertyNotSetInConstructor on Laravel testing lifecycle properties by @alies-dev in https://github.com/psalm/psalm-plugin-laravel/pull/916Factory::hasAttached rejects Eloquent\Collection from count()->create() by @alies-dev in https://github.com/psalm/psalm-plugin-laravel/pull/915Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v4.10.1...v4.10.2
Route::macro, Http::macro, Collection::macro, etc) by @alies-dev in https://github.com/psalm/psalm-plugin-laravel/pull/905Collection::random() with Laravel's actual signature by @alies-dev in https://github.com/psalm/psalm-plugin-laravel/pull/904Factory by @alies-dev in https://github.com/psalm/psalm-plugin-laravel/pull/906Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v4.10.0...v4.10.1
Factory count/times chains by @alies-dev in https://github.com/psalm/psalm-plugin-laravel/pull/878Model::query() flattening static for plain models by @alies-dev in https://github.com/psalm/psalm-plugin-laravel/pull/890.stubphp to .phpstub by @alies-dev in https://github.com/psalm/psalm-plugin-laravel/pull/891Arg helper to dedupe per-call-arg type/node access in handlers by @alies-dev in https://github.com/psalm/psalm-plugin-laravel/pull/892Builder template-arg invariants by @alies-dev in https://github.com/psalm/psalm-plugin-laravel/pull/893[@throws](https://github.com/throws) annotations on synthesised macro pseudo-methods by @alies-dev in https://github.com/psalm/psalm-plugin-laravel/pull/900 (currently blocked by Psalm)Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v3.9.2...v3.10.0
Factory count/times chains by @alies-dev in https://github.com/psalm/psalm-plugin-laravel/pull/878Model::query() flattening static for plain models by @alies-dev in https://github.com/psalm/psalm-plugin-laravel/pull/890.stubphp to .phpstub by @alies-dev in https://github.com/psalm/psalm-plugin-laravel/pull/891Arg helper to dedupe per-call-arg type/node access in handlers by @alies-dev in https://github.com/psalm/psalm-plugin-laravel/pull/892Builder template-arg invariants by @alies-dev in https://github.com/psalm/psalm-plugin-laravel/pull/893[@throws](https://github.com/throws) annotations on synthesised macro pseudo-methods by @alies-dev in https://github.com/psalm/psalm-plugin-laravel/pull/900 (currently blocked by Psalm)Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v4.9.3...v4.10.0
BelongsToMany/MorphToMany by @alies-dev in #883Rule::in(...) fluent builder to literal-string union by @alies-dev in https://github.com/psalm/psalm-plugin-laravel/pull/877Route::middleware() chain inference on Psalm dev-master by @alies-dev in https://github.com/psalm/psalm-plugin-laravel/pull/889psalm-laravel init command by @alies-dev in #881:
Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v3.9.2...v3.9.3
BelongsToMany/MorphToMany by @alies-dev in #883Rule::in(...) fluent builder to literal-string union by @alies-dev in https://github.com/psalm/psalm-plugin-laravel/pull/877Route::middleware() chain inference on Psalm dev-master by @alies-dev in https://github.com/psalm/psalm-plugin-laravel/pull/889psalm-laravel init command by @alies-dev in #881:
Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v4.9.2...v4.9.3
using/as chain mutations on belongsToMany / morphToMany by @alies-dev in https://github.com/psalm/psalm-plugin-laravel/pull/868NoEnvOutsideConfig by @alies-dev in https://github.com/psalm/psalm-plugin-laravel/pull/859PossiblyUnusedMethod:
PossiblyUnusedMethod reports for FormRequest hooks by @andrew-demb in https://github.com/psalm/psalm-plugin-laravel/pull/863PossiblyUnusedMethod suppression to common Laravel hook methods: Command, Migration, Seeder, Mailable, Notification, ServiceProvider by @alies-dev in https://github.com/psalm/psalm-plugin-laravel/pull/866Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v3.9.1...v3.9.2
using/as chain mutations on belongsToMany / morphToMany by @alies-dev in https://github.com/psalm/psalm-plugin-laravel/pull/868NoEnvOutsideConfig by @alies-dev in https://github.com/psalm/psalm-plugin-laravel/pull/859PossiblyUnusedMethod:
PossiblyUnusedMethod reports for FormRequest hooks by @andrew-demb in https://github.com/psalm/psalm-plugin-laravel/pull/863PossiblyUnusedMethod suppression to common Laravel hook methods: Command, Migration, Seeder, Mailable, Notification, ServiceProvider by @alies-dev in https://github.com/psalm/psalm-plugin-laravel/pull/866Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v4.9.1...v4.9.2
AuthManager::__call-forwarded methods (eg auth()->authenticate()) (#856) @alies-devFull Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v3.9.0...v3.9.1
AuthManager::__call-forwarded methods (eg auth()->authenticate()) (#856) @alies-devFull Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v4.9.0...v4.9.1
This release is focused respecting validation rules when to mark input as safe.
FormRequest input() / string() / str() accessors (#821) @alies-dev[@psalm-taint-escape](https://github.com/psalm-taint-escape) on custom Rule classes (#826) @alies-devscopes() chaining on builder contracts (#846) @alies-devfirstOr() callback template across argument positions (#851) @alies-devvendor/bin/psalm-laravel CLI with init subcommand for first-time plugin setup (#786) @alies-devpsalm-laravel add subcommand to scaffold a GitHub Actions security-analysis workflow (#814) @alies-devCollection, Session, RedirectResponse, LazyCollection, MessageBag, ServiceProvider(#809, #832) @alies-devCollection::make / LazyCollection::make / collect() for scalar inputs (#783) @alies-devInvalidArgument on arrow-function closures in the Builder::where family (#784) @alies-devonly / except / collect / old to the correct traits (#825) @alies-devimplements / extends in 4 stubs that were wiping reflected metadata (#835, #836) @alies-devStatsHandler to report plugin-level counts under psalm --stats (#817) @alies-devThis release is focused respecting validation rules when to mark input as safe.
FormRequest input() / string() / str() accessors (#821) @alies-dev[@psalm-taint-escape](https://github.com/psalm-taint-escape) on custom Rule classes (#826) @alies-devscopes() chaining on builder contracts (#846) @alies-devfirstOr() callback template across argument positions (#851) @alies-devvendor/bin/psalm-laravel CLI with init subcommand for first-time plugin setup (#786) @alies-devpsalm-laravel add subcommand to scaffold a GitHub Actions security-analysis workflow (#814) @alies-devCollection, Session, RedirectResponse, LazyCollection, MessageBag, ServiceProvider(#809, #832) @alies-devCollection::make / LazyCollection::make / collect() for scalar inputs (#783) @alies-devInvalidArgument on arrow-function closures in the Builder::where family (#784) @alies-devonly / except / collect / old to the correct traits (#825) @alies-devimplements / extends in 4 stubs that were wiping reflected metadata (#835, #836) @alies-devStatsHandler to report plugin-level counts under psalm --stats (#817) @alies-devBackport changes from v4.8.2- v4.8.4 releases
AuthManager instance calls, not just the Auth facade (#773) @alies-devStringable-accepting stubs to reduce ImplicitToStringCast false positives (#775) @alies-devfilled() to narrow ?string to non-empty-string (#762) @alies-devapp(static::class, ...) and class-string<Foo> arguments (#754) @alies-devModel subclasses (e.g. Laravel Scout) (#769) @alies-devNoEnvOutsideConfig from firing inside analysed project's config/ (#767) @alies-devDocblockTypeContradiction on filled()/blank() guards with nullable strings (#753) @alies-devFull Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v3.8.1...v3.8.4
This release focuses on fixing minor issues (plugin gaps) found on real projects.
AuthManager instance calls, not just the Auth facade (#773) @alies-devStringable-accepting stubs to reduce ImplicitToStringCast false positives (#775) @alies-devfilled() to narrow ?string to non-empty-string (#762) @alies-devModel subclasses (e.g. Laravel Scout) (#769) @alies-devNoEnvOutsideConfig from firing inside analysed project's config/ (#767) @alies-devFull Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v4.8.3...v4.8.4
DocblockTypeContradiction on filled()/blank() guards with nullable strings (#753) @alies-devapp(static::class, ...) and class-string<Foo> arguments (#754) @alies-devFull Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v4.8.2...v4.8.3
IssueUrlGenerator (#747) @alies-dev--SKIPIF-- support to PsalmTest via getSkipReason() (#742) @alies-devalies-dev/psalm-tester) @alies-devFull Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v4.8.1...v4.8.2
Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v3.8.0...v3.8.1
v3.8.0 is a full backport of 4.x features to the Laravel 11–12 + Psalm 6 support line. Everything that shipped in v4.x–4.8 is now available on 3.x.
From this release, almost all new features will be released on both 3.x and 4.x branches.
This release adds psalm-security-analysis AI skill installable via Laravel Boost. You can install it by re-running boost:update (or boost:install on fresh installations).
This allows your agent find and fix security issues found by Psalm taint analysis.
BelongsToMany and MorphToMany stubs now declare 4 template params — migrate BelongsToMany<T, U> → BelongsToMany<T, U, Pivot, 'pivot'> (#716)
If you documented such relationships directly in your codebase, the migration process is straightforward:
./vendor/bin/psalter --plugin=vendor/psalm/plugin-laravel/tools/psalter/UpgradeRelationAnnotations.php
env('KEY', $default) return type based on the default argument — env('KEY', 'val') → string, env('KEY', false) → string|false (#712)Auth::guard('web') to its concrete class (SessionGuard, TokenGuard) from auth.php config (#711)Collection::whereNotNull() to remove null from TValue when called without a key (#713)withCount/withExists/withSum/withMin/withMax/withAvg aggregate accessor properties on Eloquent models without UndefinedMagicPropertyFetch (#715)dispatch() and dispatchIf() arguments against the job/event constructor signature (#726)Carbon (or a custom date class from Date::use()) for now() and today() helpers (#725)SoftDeletes methods (withTrashed, onlyTrashed, withoutTrashed) on base Builder instances (#727)HigherOrderCollectionProxy method call chains precisely — $users->sortByDesc->method() no longer produces InvalidMethodCall (#724)Config::array() and Config::collection() $default argument — scalar fallbacks emit InvalidArgument (#736)<dynamicWhereMethods value="true" /> config to resolve where{Column} calls on relation chains (#714)Http\Client\Response body/header methods (body(), json(), header(), etc.) as taint sources (#676)[@psalm-taint-sink](https://github.com/psalm-taint-sink) file to ~30 path-accepting methods in Filesystem, FilesystemAdapter, and LockableFile (#739)collect() with no arguments to return Collection<never, never> instead of Collection<array-key, mixed> (#722)Collection::empty() to return static<never, never>, assignable to any typed collection (#679)Collection::sum() from mixed to int|float (#680)Conditionable::when()/unless() and Tappable::tap() returning mixed on fluent chains — now returns $this (#710)$col->map, $col->each) to carry concrete TKey/TValue types (#720)Str::replace() return to string when the subject is a string (#719)[@psalm-this-out](https://github.com/psalm-this-out) to paginator setCollection() to narrow the type after item replacement (#688)$user->posts()->published()->get() now resolves correctly (#738)TaintedHtml from Blade view $data parameters — Blade auto-escapes {{ $var }} output (#690)TaintedSql from PDO-parameterized Builder methods (where, find, having, etc.) (#691)PossiblyUnusedMethod false positives for legacy Eloquent getXxxAttribute()/setXxxAttribute() methods (#732)Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v3.4.0...v3.8.0
compact output mode to save tokens (available from Psalm v7.0.beta-18)E_USER_DEPRECATED output type for deprecation warningsFull Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v4.8.0...v4.8.1
This release adds psalm-security-analysis AI skill installable via Laravel Boost. You can install it by re-running boost:update (or boost:install on fresh installations).
This allows your agent find and fix security issues found by Psalm taint analysis.
psalm-security-analysis skill for AI agents (#672) @alies-devHigherOrderCollectionProxy method call chains (#724) @alies-devConfig::array() and Config::collection() default argument type (#736) @alies-dev[@psalm-taint-sink](https://github.com/psalm-taint-sink) file methods to Filesystem/FilesystemAdapter stubs (#739) @alies-devPossiblyUnusedMethod false positives for Eloquent legacy accessor/mutator methods (#732) @alies-devSimplify migration: Add Psalter plugin for upgrading relation PHPDoc annotations (#735) @alies-dev
Migration of PHPDoc from v3.x version to v4.7+ for relationship definitions is easy now:
./vendor/bin/psalter --plugin=vendor/psalm/plugin-laravel/tools/psalter/UpgradeRelationAnnotations.php
Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v4.7.0...v4.8.0
This release focuses on Eloquent Builder type coverage — new aggregate accessor resolution (withCount/withExists/withSum/withMin/withMax/withAvg), SoftDeletes on Builder instances, where{Column} dynamic methods on builder and relations, and several stub fixes that restore correct pivot types.
where{Column} method resolution on relation chains (#714)withCount/withExists/withSum/withMin/withMax/withAvg aggregate accessor properties on Eloquent models (#715)SoftDeletes methods resolving on base Builder instances (#727)BelongsToMany/MorphToMany stubs to declare 4 template params, restoring pivot types in return values (#716)Carbon for now() and today() helpers (#725)dispatch() arguments against job/event constructor signature (#726)Auth::guard() return type to the concrete guard class (#711)Collection::whereNotNull() to remove null from TValue (#713)env() return type based on the default value argument (#712)collect() with no args to return Collection<never, never> (#722)[@property-read](https://github.com/property-read) stubs for higher-order collection proxies (#720)Str::replace() (#719)Conditionable::when()/unless() and Tappable::tap() to fix mixed return on fluent chains (#710)Collection::empty() and Collection::sum() return types (#683)[@psalm-this-out](https://github.com/psalm-this-out) to paginator setCollection() (#688)[@psalm-taint-escape](https://github.com/psalm-taint-escape) sql to parameterized Builder methods (#691)TaintedHtml sinks from Blade view data (#690)Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v4.6.2...v4.7.0
[@psalm-taint-source](https://github.com/psalm-taint-source) input for Http\Client\Response methods (#676) @alies-devCollection::sum() return type from mixed to int|float (#680) @alies-devCollection::empty() stub with static<never, never> return type (#679) @alies-devFull Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v4.6.1...v4.6.2
Backports type inference and taint analysis improvements from Plugin 4.x to Psalm 6 users.
CookieJar make/queue/forever/forget methods flagged as taint-sink headerStorage::put(), Storage::prepend(), Storage::append() as path/file sinksHttp::get(), Http::post(), Http::send() as SSRF sinkssession() helper and Store methods as taint sources (XSS, SQL injection)View::make(), view() helper, View::share() as HTML sinksMailable subject/to/from as header sinks, body/line/action as HTML sinkseval, evalSha, executeRaw as eval sinksfilename, path, contents, MIME type as taint sourcesencrypt()/decrypt() correctly modeled as taint escape/unescapeheader(), withHeaders(), cookie() as header sinksStubs backported from v4.0–v4.6 to reduce false positives:
count → int<0,max>, get → Collection<int, stdClass>, cursor → LazyCollection), added 20+ method stubs (whereNot, having, from, orderBy, etc.)cursor, pluck, paginators, firstOrCreate; added whereNot, createOrFirst, findSole, chunkMap; @psalm-variadic on with()/without()Stringable/HasBroadcastChannel implements, public increment/decrementBlueprint, ColumnDefinition, ForeignIdColumnDefinition, ForeignKeyDefinition (fluent migration chains)Authenticatable, SessionGuard, TokenGuardfilter() without callback now removes null/false from TValue; flatten(1)/collapse() preserve TValueFull Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v3.3.0...v3.4.0
__callStatic) Model calls (#663) @alies-devRelation method calls (#661) @alies-devFull Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v4.6.0...v4.6.1
Deep relationship type resolution, Custom query builders, Custom Collections, and smarter validation shapes (thanks to @MDG11).
#[UseEloquentBuilder] attribute and newEloquentBuilder() override (#621) @alies-devSoftDeletes trait methods on custom query builders (#632) @alies-devMethodForwardingHandler for Relation method forwarding (#642) @alies-devmorphTo property type from docblock generic annotations (#652) @alies-dev#[CollectedBy] attribute for custom Eloquent collections (#623) @alies-devCollection::flatten() and collapse() return types to preserve TValue (#619) @alies-devModel::increment()/decrement() as public in stub (#618) @alies-devModelMakeDiscouraged when model has custom make() method (#616) @alies-dev[@psalm-flow](https://github.com/psalm-flow) for Collection get()/first()/pull()/value() default parameter taint propagation (#650) @alies-devFull Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v4.5.0...v4.6.0
Three new opt-in rules, expanded taint coverage, and fewer false positives across the board (focus on __() and trans()).
MissingView: Detect missing Blade view files in view() and View::make() calls (#579) @alies-devModelMakeDiscouraged: Detect undefined translation keys in __() and trans() calls (#595) @alies-devMissingTranslation: Warn against Model::make() in favor of new Model() @alies-dev__() and trans() return type to string|array (was mixed) (#592) @alies-dev__() return to string when the translation key is known to exist @alies-devMissingTemplateParam on HasFactory trait (#517) @alies-devModel (#498) @alies-devimplements clauses to 15 stubs (#615) @alies-devmorphTo stub to bypass $this issue in generics @alies-devmorphToMany/morphedByMany signatures @alies-dev[@return](https://github.com/return) static to Stringable stub methods @alies-dev[@psalm-taint-source](https://github.com/psalm-taint-source) input for Route parameter methods (#608) @alies-deveval/executeRaw (Lua injection) @alies-devCookieJar methods @alies-dev$path/$domain sinks to Cookie::expire() and forget() @alies-devStr::of(), str(), and Stringable @alies-devHash::make() and bcrypt() as [@psalm-taint-escape](https://github.com/psalm-taint-escape) system_secret @alies-devTested against 10 real-world Laravel apps (bagisto, coolify, monica, pixelfed, solidtime, unit3d, vito, and others). Combined results vs v4.4.0:
| Metric | v4.4.0 | v4.5.0 | Delta |
|---|---|---|---|
| Total issues | 84,503 | 76,123 | -9.9% |
| Plugin-caused false positives | 5,115 | 4,155 | -18.8% |
| Security findings (taint) | 83 | 84 | +1 |
Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v4.4.0...v4.5.0
Collection, Model, Builder stubs (backport them from 4.x) @alies-devFull Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v3.2.2...v3.3.0
Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v3.2.1...v3.2.2
MorphTo relationships @alies-devFull Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v3.2.0...v3.2.1
Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v3.1.5...v3.2.0
This is the biggest release since v4.0.
Is release is focused on Validator and FormRequest classes and provides best-in-class type infer for them.
FormRequest (#577) @alies-devView\Factory and View\View methods (#580) @alies-devJs::from() and Js::encode() (#573) @alies-devSession\Store::get() and other) (#557) @alies-devMail and Notification classes (#556) @alies-devRequest::integer() and Request::float() (#575) @alies-devFull Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v4.3.2...v4.4.0
| Dependency | plugin v3 | plugin v4 |
|---|---|---|
| PHP | ^8.2 | ^8.2 |
| Laravel | 11, 12 | 12, 13 |
| Psalm | 6, 7 (beta) | 7 only |
Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v4.3.1...v4.3.2
Attribute::make() (#552)[@psalm-taint-escape](https://github.com/psalm-taint-escape) html for e() helper to avoid false negatives (#551)stubs/taintAnalysis/ into stubs/common/ (#553)Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v4.3.0...v4.3.1
This release focuses on migration schema analysis for better Eloquent attribute type inference.
Schema call patterns (connection chaining, class constants, custom facades) (#526)foreignIdFor() column type from referenced model's primary key (#523)Blueprint::datetimes() and fix ulid() default column name (#531)mixed type for unknown Blueprint methods (custom DB types added by macros) (#528)Collection::map() return type, add Builder::select() and ResponseTrait::cookie() stubs (#548)[@psalm-taint-escape](https://github.com/psalm-taint-escape) sql for Connection::escape() (#547)UploadedFile and encrypt/decrypt helpers (#546)Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v4.2.0...v4.3.0
Relationship accessors without generics — The plugin now resolves Eloquent relationship property types even when methods lack generic annotations. Previously, $user->posts required [@return](https://github.com/return) HasMany<Post, User> to get a precise type. Now the plugin parses the method body AST to extract the related model from $this->hasMany(Post::class), falling back gracefully to bounded types.
Static Query Builder methods on Models — User::where(...), User::orderBy(...), and model scopes now resolve with the correct Builder<User> return type, enabling full type inference through query chains starting from the model class.
SQL schema dump support — The plugin now parses php artisan schema:dump output (MySQL, PostgreSQL, SQLite) as a base layer for model attribute discovery. PHP migrations are applied on top, matching Laravel's own resolution order.
🛡️ Security: new taint sinks — Added XSS detection through HtmlString (which bypasses Blade escaping) and path traversal detection through Storage facade methods (put, writeStream, delete, copy, move, etc.).
Query\Builder methods and scopes on Model classes (#508)Schema\ColumnDefinition, ForeignIdColumnDefinition, and ForeignKeyDefinition fluent methods (#501)HtmlString to detect XSS bypass of Blade escaping (#491)Storage facade / FilesystemAdapter path traversal detection (#492)up() (#509)nullableTimestampsTz() switch case in schema aggregatorcount/update/increment/decrement return type to int<0, max> (#499)hasUserPseudoProperty() helper to reduce redundant storage lookups$codebase->progress->debug() to relationship resolution catch blocks for --debug traceabilityfindStubFiles() — errors now propagate to the top-level handlerFull Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v4.1.0...v4.2.0
pluck() value type from model [@property](https://github.com/property) annotations (#488) @alies-devFull Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v4.0.1...v4.1.0
Collection::filter() return type when called without callback (#467) @alies-devroute helper function stub as not needed anymore @alies-devonce helper function stub as not needed anymore @alies-devFull Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v4.0.0...v4.0.1
The biggest release since the plugin was created. 90% of the codebase was rewritten for stability, performance, and deeper Laravel coverage.
./vendor/bin/psalmAttribute<TGet, TSet> templates all work. Use both tools together: Larastan for types, psalm-plugin-laravel for securitybarryvdh/laravel-ide-helper dependency -- facades and model properties are now resolved natively by the pluginInvalidConsoleArgumentName,
InvalidConsoleOptionName,
NoEnvOutsideConfig[@property](https://github.com/property) declarations take precedence over migration-discovered propertiescasts() parsing without method executionscopeXxx() methods and Laravel 12+ #[Scope] attribute, plus the Scope interfaceafter() closures, Blueprint::rename(), addColumn(), vector columns, and auto-discovery of directories registered via loadMigrationsFrom()| Dependency | v3 | v4 |
|---|---|---|
| PHP | ^8.2 | ^8.3 |
| Laravel | 11, 12 | 12, 13 |
| Psalm | 6, 7 (beta) | 7 only |
Eloquent relation generics now require a declaring model parameter (e.g., BelongsTo<Foo> becomes BelongsTo<Foo, self>).
composer require --dev psalm/plugin-laravel:^4.0 -W
psalm-plugin-laravel is the only free tool that combines Laravel-aware type analysis with dataflow-based taint vulnerability detection:
| Vulnerability | Laravel surface | OWASP |
|---|---|---|
| SQL Injection | DB::statement(), DB::unprepared(), query builder raw methods |
A03:2021 |
| Shell Injection | Process::run(), Process::pipe() |
A03:2021 |
| File Traversal | Storage::get(), Storage::put(), 15 Filesystem methods |
A01:2021 |
| SSRF | Http::get(), Http::post(), 6 HTTP client methods |
A10:2021 |
| XSS | Response::setContent(), ResponseFactory::make() |
A03:2021 |
| Open Redirect | Redirect::to(), Redirect::away() |
A10:2021 |
| Crypto tracking | Encrypter, HashManager taint-escape/unescape |
A02:2021 |
Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v3.1.5...v4.0.0
See v4.0.0 Beta 1 release for full list of major changes
composer require --dev psalm/plugin-laravel:^4.0@beta -W
If you have "minimum-stability": "stable", and got Your requirements could not be resolved to an installable set of packages.: error
composer config minimum-stability beta
composer config prefer-stable true
composer require --dev vimeo/psalm:^7.0@beta psalm/plugin-laravel:^4.0@RC -W
See Upgrading from v3 to v4 for details.
RC Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v4.0.0-rc.1...v4.0.0-rc.2
Major Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v3.1.5...v4.0.0-rc.2
See v4.0.0 Beta 1 release for full list of major changes
composer require --dev psalm/plugin-laravel:^4.0@beta -W
If you have "minimum-stability": "stable", and got Your requirements could not be resolved to an installable set of packages.: error
composer config minimum-stability beta
composer config prefer-stable true
composer require --dev vimeo/psalm:^7.0@beta psalm/plugin-laravel:^4.0@RC -W
See Upgrading from v3 to v4 for details.
NoEnvOutsideConfig rule to detect env() calls outside config directory https://github.com/psalm/psalm-plugin-laravel/pull/456CastsMethodParser AST traversal — getAttribute('parent') is always null https://github.com/psalm/psalm-plugin-laravel/pull/458createMany param types and CastsMethodParser AST parent lookup https://github.com/psalm/psalm-plugin-laravel/pull/462RC/Beta Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v4.0.0-beta.2...v4.0.0-rc.1
Major Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v3.1.5...v4.0.0-rc.1
Beta Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v4.0.0-beta.1...v4.0.0-beta.2
Major Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v3.1.5...v4.0.0-beta.2
[@property](https://github.com/property) declarations (take precedence over migration-discovered properties)scopeXxx() and #[Scope] attribute)barryvdh/laravel-ide-helper dependency — facades and model properties are now resolved nativelycomposer require --dev psalm/plugin-laravel:^4.0@beta -W
Major Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v3.1.5...v4.0.0-beta.1
dropColumn() with array argument in SchemaAggregator (#448) @alies-devFull Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v3.1.4...v3.1.5
retry() helper @alies-devFull Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v3.1.3...v3.1.4
unsignedBigInteger, increments, foreignId, id, and the ->unsigned() modifier are now recognized, enabling non-negative-int inference for unsigned columns @alies-dev->default() calls in migrations are now parsed and tracked, enabling more accurate type inference for model attributes with defaults @alies-devif blocks) inside migration closures no longer cause subsequent column definitions to be skipped @alies-devforeignIdFor() column name: foreignIdFor(User::class) now correctly resolves to user_id instead of id @alies-devFull Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v3.1.2...v3.1.3
barryvdh/laravel-ide-helper dependency @alies-devFull Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v3.1.1...v3.1.2
Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v3.1.0...v3.1.1
Taint Analysis: Security Analysis in Psalm. Example 1, Example 2
retry() helper (#417) @alies-devFull Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v3.0.5...v3.1.0
Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v3.0.4...v3.0.5
dispatch() match upstream Laravel (#407) @saulens22Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v3.0.3...v3.0.4
dev to composer keywords (#397) @alies-devInternal changes:
Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v2.12.1...v2.12.2
How can I help you explore Laravel packages today?