psalm/plugin-laravel
Laravel Psalm plugin for deep static analysis plus taint-based security scanning. Detect SQL injection, XSS, SSRF, shell injection, path traversal, and open redirects by tracking user input through Laravel code—without executing it. Complements Larastan/PHPStan.
$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?