Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Plugin Laravel Laravel Package

psalm/plugin-laravel

Laravel static analysis with built-in taint-based security scanning: traces user input from request to sink to catch SQL injection, XSS, shell injection, file traversal, SSRF, open redirects and timing-unsafe secret comparisons. Runs locally and in CI, no account or cloud upload.

View on GitHub
Deep Wiki
Context7
v3.17.0

Two opt-in rules that catch typos in route names and filesystem disks, plus sharper inference for Eloquent relations, aggregate attributes, when()/unless() callbacks, and Collection::filter().

Features

  • Add opt-in UnregisteredRouteName rule for route names missing from the booted app's route table: route(), to_route(), URL::route()/signedRoute()/temporarySignedRoute(), Redirect::route(), redirect()->route(), url()->route(). Enable with <findUnregisteredRouteNames value="true" /> or <experimental value="true" /> (#1573)
 return redirect()->route('users.shwo', $user);
+// UnregisteredRouteName: Route name 'users.shwo' is not registered
  • Add opt-in UnconfiguredFilesystemDisk rule for Storage::disk() names absent from filesystems.disks, with a closest-name suggestion. Enable with <findUnconfiguredFilesystemDisks value="true" /> or <experimental value="true" />. \Storage::disk() now also narrows to FilesystemAdapter like the facade (#1571)
 Storage::disk('publik')->put($path, $contents);
+// UnconfiguredFilesystemDisk: Disk 'publik' is not configured in filesystems.disks, did you mean 'public'?
  • Type when()/unless() callback parameters from the condition value: when() passes the truthy value, unless() the falsy one, and a Closure condition passes its return type (#1645)
 /** [@var](https://github.com/var) ?int $limit */
 $query->when($limit, function (Builder $q, string $limit) { /* ... */ });
-// no issue: the callback parameter was never checked
+// InvalidArgument: when expects callable(Builder<...>, int):mixed|null, but Closure(Builder, string):void provided
  • Infer relation generics for relation methods declared in a trait or delegating to another relation method (return $this->allRevisions()->where(...)) (#1646)
 // HasPaymentSlips trait: public function paymentSlips(): MorphMany { return $this->morphMany(PaymentSlip::class, 'owner'); }
 $member->paymentSlips();
-// MorphMany<Model, Model>
+// MorphMany<PaymentSlip, Member>
  • Narrow Collection::filter(callable) like where(callable), and recognize is_subclass_of(), is_a(), is_scalar(), is_iterable(), is_resource() and null checks (!is_null($x), $x !== null) in both (#1651)
 $items->filter(fn (Countable $x): bool => $x instanceof Stringable);
-// Collection<int, Countable>
+// Collection<int, Countable&Stringable>
 $classes->filter(fn (string $c): bool => is_subclass_of($c, Job::class));
-// Collection<int, string>
+// Collection<int, class-string<Job>>

Fixes

  • Type {relation}_count / {relation}_exists as nullable unless withCount()/withExists()/loadCount()/loadExists() is proven on that model (or the relation is in $withCount), and type withMin/withMax/withSum/withAvg attributes from the related column's type (#1628)
 $post->comments_count ?? $post->loadCount('comments')->comments_count;
-// RedundantCondition: comments_count was always int
+// no issue: int|null until a count is proven loaded
  • Mark Model as [@psalm-consistent-constructor](https://github.com/psalm-consistent-constructor), so new $class() on a class-string<Model> is no longer UnsafeInstantiation. A model overriding __construct with an incompatible signature is now reported, since Eloquent's own new static($attributes) already breaks on it (#1626)
 /** [@param](https://github.com/param) class-string<Model> $class */
 function make(string $class): Model { return new $class(); }
-// UnsafeInstantiation
+// no issue
  • Accept any Collection in BelongsToMany::sync(), syncWithoutDetaching(), syncWithPivotValues() and their Laravel 13 *OrFail() variants (#1644)
 $user->roles()->sync($roles->pluck('id'));
-// InvalidArgument / ImplicitToStringCast
+// no issue
  • Keep $this as the declaring model for relation calls inside non-final models, so return $this->rel()->where(...) no longer reports MoreSpecificReturnType / LessSpecificReturnStatement (#1629, #1632)
  • Resolve scopes and SoftDeletes methods per receiver model on a generic custom builder shared by a parent and child model, instead of whichever model was scanned last (#1638)
  • Drop [@psalm-mutation-free](https://github.com/psalm-mutation-free) from Request::input(), which lazily decodes JSON: a FormRequest overriding input() is no longer flagged (#1627)
  • Stop analysis-time type checks from autoloading analyzed classes, which crashed the run or silently disabled the plugin when a vendor file raised a load-time deprecation (#1661)
  • Fix a crash on first-class callables ($request->input(...), Artisan::command(...), Job::dispatch(...)) with zend.assertions=1 (#1659)

Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v3.16.6...v3.17.0

v3.16.6

v3.16.6 restores vendor methods on stubbed Laravel classes that silently resolved to mixed, and tightens docblock types for console, events, facades, mail, and relations.

Fixes

  • Fix unstubbed methods of a stubbed Laravel class resolving to mixed when nothing in the project named that class before the plugin's stubs loaded. Psalm then skipped the class's vendor file, so with __call (Macroable) every method the stub did not declare became mixed with no issue reported. Affected classes included Events\Dispatcher, Http\Client\PendingRequest, Http\Client\Response, FormRequest, Pipeline, and several facades (#1616, #1618)
 app('events')->hasListeners('order.shipped');
-// mixed
+// bool
  • Type Artisan::all() and Console\Kernel::all() (contract and concrete) as array<array-key, Command> instead of array (#1612)
 $commands = Artisan::all();
-// array<array-key, mixed>
+// array<array-key, Symfony\Component\Console\Command\Command>
  • Type DB::getQueryLog() entries and Schema::getColumnListing() with the shapes Connection and Schema\Builder already document, instead of the facades' plain array (#1612)
 $log = DB::getQueryLog();
-// array<array-key, mixed>
+// list<array{query: string, bindings: array<array-key, mixed>, time: float|null, readWriteType?: string|null}>
  • Type halting event dispatch (until(), dispatch() with $halt = true) on Events\Dispatcher, its contract, and the Event facade as mixed: it returns the first non-null listener response, not array|null (#1612)
  • Type MailMessage properties ($view nullable; $from, $replyTo, $attachments, $rawAttachments with the shapes MailChannel accepts), MailMessage::render() as HtmlString|string, and Notification::$id as string|null (#1612)
  • Accept an Eloquent Collection of related models in BelongsToMany::saveMany() and saveManyQuietly(), and keep its type on return (#1612)
  • Type Reflector::getParameterClassNames() as list<string>, Route::methods() as array<string>, accept null in Migrator::usingConnection(), and mark UrlGenerator's resolver, formatter, and forced root/scheme properties nullable (#1612)

Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v3.16.4...v3.16.6

v3.15.6

Two new inference sources for Eloquent-backed code, one new opt-in queue-safety rule, and three false-positive fixes across migrations, model attribute helpers, and pipelines.

Features

  • Add opt-in SerializedQueuedModel rule for ShouldQueue classes that hold an Eloquent model without reaching Illuminate\Queue\SerializesModels, where the whole model is written into the queue payload instead of a ModelIdentifier (#1385). Enable with <findSerializedQueuedModels value="true" />. Detection resolves the flattened __serialize() / __sleep() rather than matching the trait name, so framework bases that already pull the trait in (Illuminate\Foundation\Queue\Queueable, Illuminate\Notifications\Notification) and classes that hand-write their own serialization stay silent.
 final class ReconcileLedger implements ShouldQueue
 {
     public function __construct(private Customer $customer) {}
-    // silent: the entire Customer row is written into the queue payload
+    // SerializedQueuedModel: $customer will be serialized whole into the queue payload
 }
  • Infer Arr::pluck() value and key types from the element model's [@property](https://github.com/property) annotations, matching what Collection::pluck() and Builder::pluck() already do (#1383).
 /** [@param](https://github.com/param) list<Customer> $rows */   // Customer has [@property](https://github.com/property) string $id
-Arr::pluck($rows, 'id');        // array<array-key, mixed>
+Arr::pluck($rows, 'id');        // list<string>
-Arr::pluck($rows, 'id', 'id');  // array<array-key, mixed>
+Arr::pluck($rows, 'id', 'id');  // array<string, string>

Fixes

  • Support $schema = Schema::connection(...) in migrations, so columns declared through a variable-held builder (#1382).
  • Narrow Model::getAppends() and Model::getMutatedAttributes() to list<string>, so callers no longer have to re-assert the element type (#1381).
  • Type Pipeline::then() from its destination closure instead of leaving it mixed (#1376).

Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v3.15.5...v3.15.6

v4.15.6

Two new inference sources for Eloquent-backed code, one new opt-in queue-safety rule, and three false-positive fixes across migrations, model attribute helpers, and pipelines.

Features

  • Add opt-in SerializedQueuedModel rule for ShouldQueue classes that hold an Eloquent model without reaching Illuminate\Queue\SerializesModels, where the whole model is written into the queue payload instead of a ModelIdentifier (#1385). Enable with <findSerializedQueuedModels value="true" />. Detection resolves the flattened __serialize() / __sleep() rather than matching the trait name, so framework bases that already pull the trait in (Illuminate\Foundation\Queue\Queueable, Illuminate\Notifications\Notification) and classes that hand-write their own serialization stay silent.
 final class ReconcileLedger implements ShouldQueue
 {
     public function __construct(private Customer $customer) {}
-    // silent: the entire Customer row is written into the queue payload
+    // SerializedQueuedModel: $customer will be serialized whole into the queue payload
 }
  • Infer Arr::pluck() value and key types from the element model's [@property](https://github.com/property) annotations, matching what Collection::pluck() and Builder::pluck() already do (#1383).
 /** [@param](https://github.com/param) list<Customer> $rows */   // Customer has [@property](https://github.com/property) string $id
-Arr::pluck($rows, 'id');        // array<array-key, mixed>
+Arr::pluck($rows, 'id');        // list<string>

Fixes

  • Support $schema = Schema::connection(...) in migrations, so columns declared through a variable-held builder (#1382).
  • Narrow Model::getAppends() and Model::getMutatedAttributes() to list<string>, so callers no longer have to re-assert the element type (#1381).
  • Type Pipeline::then() from its destination closure instead of leaving it mixed (#1376).

Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v4.15.5...v4.15.6

v3.15.5

Taint precision and Eloquent type narrowing. Two taint fixes remove a duplicate finding and close a missed SQL injection, and four type fixes sharpen inference on facades, collections, and the query builder.

Fixes

  • Restore plugin facade stub precedence over Laravel's generated [@method](https://github.com/method) tags, so templated facade methods resolve their real return type (#1370)
 $value = Cache::remember('key', 60, fn (): User => User::first());
-// mixed — the generated [@method](https://github.com/method) tag outranked the plugin's stub
+// User
  • Narrow groupBy() and keyBy() keys for model columns instead of widening to array-key (#1369)
 $byId = User::all()->keyBy('id');
-// Collection<array-key, User>
+// Collection<int, User>
  • Type the chunkById() callback like chunk(), so the chunk is a templated collection rather than mixed (#1373)
 Article::query()->chunkById(100, function ($chunk) { ... });
-// $chunk: mixed
+// $chunk: Collection<int, Article>
  • Accept Laravel 13's fourth $fetchUsing argument on DB::select(), DB::selectResultSets(), DB::cursor(), and their Connection counterparts, while keeping the three-argument signature an error on Laravel 12 (#1374)
 DB::cursor('select * from users', [], true, [PDO::FETCH_ASSOC]);
-// TooManyArguments — the stub declared three parameters
+// accepted on Laravel 13; still TooManyArguments on Laravel 12

Row types follow the fetch mode, so a custom $fetchUsing no longer claims stdClass:

 $rows = DB::cursor('select 1');                              // Generator<int, stdClass>
 $assoc = DB::cursor('select 1', [], true, [PDO::FETCH_ASSOC]);
-// Generator<int, stdClass>
+// Generator<int, mixed>
  • 🛡️ Report a tainted view name as TaintedInclude only, not also TaintedFile (#1358). A view name selects which template executes; it cannot reach an arbitrary path, because the view finder rewrites . to / and appends a fixed extension
  • 🛡️ Key WhereColumnTaintHandler's removal bridge on the AST node itself rather than spl_object_id, closing a missed SQL injection (#1366). Psalm frees foreign ASTs mid-file, so a reissued object handle could hit a stale record and strip sql taint from an unrelated expression
  • Ship the Psalm cache in the generated CI template, pass both thread flags, and install igbinary (#1347)

Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v3.15.4...v3.15.5

v4.15.5

Taint precision and Eloquent type narrowing. Two taint fixes remove a duplicate finding and close a missed SQL injection, and four type fixes sharpen inference on facades, collections, and the query builder.

Fixes

  • Restore plugin facade stub precedence over Laravel's generated [@method](https://github.com/method) tags, so templated facade methods resolve their real return type (#1370)
 $value = Cache::remember('key', 60, fn (): User => User::first());
-// mixed — the generated [@method](https://github.com/method) tag outranked the plugin's stub
+// User
  • Narrow groupBy() and keyBy() keys for model columns instead of widening to array-key (#1369)
 $byId = User::all()->keyBy('id');
-// Collection<array-key, User>
+// Collection<int, User>
  • Type the chunkById() callback like chunk(), so the chunk is a templated collection rather than mixed (#1373)
 Article::query()->chunkById(100, function ($chunk) { ... });
-// $chunk: mixed
+// $chunk: Collection<int, Article>
  • Accept Laravel 13's fourth $fetchUsing argument on DB::select(), DB::selectResultSets(), DB::cursor(), and their Connection counterparts, while keeping the three-argument signature an error on Laravel 12 (#1374)
 DB::cursor('select * from users', [], true, [PDO::FETCH_ASSOC]);
-// TooManyArguments — the stub declared three parameters
+// accepted on Laravel 13; still TooManyArguments on Laravel 12

Row types follow the fetch mode, so a custom $fetchUsing no longer claims stdClass:

 $rows = DB::cursor('select 1');                              // Generator<int, stdClass>
 $assoc = DB::cursor('select 1', [], true, [PDO::FETCH_ASSOC]);
-// Generator<int, stdClass>
+// Generator<int, mixed>
  • 🛡️ Report a tainted view name as TaintedInclude only, not also TaintedFile (#1358). A view name selects which template executes; it cannot reach an arbitrary path, because the view finder rewrites . to / and appends a fixed extension
  • 🛡️ Key WhereColumnTaintHandler's removal bridge on the AST node itself rather than spl_object_id, closing a missed SQL injection (#1366). Psalm frees foreign ASTs mid-file, so a reissued object handle could hit a stale record and strip sql taint from an unrelated expression
  • Ship the Psalm cache in the generated CI template, pass both thread flags, and install igbinary (#1347)

Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v4.15.4...v4.15.5

v3.15.4

Extends taint reporting to the call forms Laravel applications actually write — facade statics, response() on its contract, and view() names — and clears the false positives that surfaced alongside it.

Features

  • 🛡️ Report taint sinks on facade static calls, response() contract methods, and view() names (#1318). A facade's surface is [@method](https://github.com/method) pseudo-methods resolved through __callStatic, whose parameters have no docblock to carry a sink, so the static form was silent while the chained form fired.
 Redirect::to($request->input('next'));
-// silent: the facade pseudo-parameter carried no sink
+// TaintedHeader: Detected tainted header
  • Detect undefined relations in model eager-load defaults $with and $withCount (#1321). Supports dotted paths, column selectors, and the $withCount alias grammar; defers when an intermediate related model cannot be resolved.
 class Post extends Model
 {
     protected $with = ['auther'];
-    // silent: eager-load defaults were never validated
+    // UndefinedModelRelation: relation 'auther' is not defined on Post
 }

Fixes

  • Honour [@psalm-taint-escape](https://github.com/psalm-taint-escape) on closure validation rules (#1352), in inline validate() arrays, in FormRequest rules(), and when the closure is the field's whole rule. Previously the only way to assert a rule made a value safe was to extract it into a dedicated Rule class.
 $request->validate([
     'path' => ['required', /** [@psalm-taint-escape](https://github.com/psalm-taint-escape) file */ static fn ($attr, $value, $fail) => /* ... */],
 ]);
-// TaintedFile: a closure body is opaque, so no rule could assert safety
+// clean
  • Fix TaintedSql false positives on where() array values for nullable, template-bounded, and intersection builder receivers (#1338, #1350). Values in the map form are PDO-bound, but a receiver typed Builder|null, [@template](https://github.com/template) T of Builder, or T&Builder declined the strip.
 /** [@param](https://github.com/param) Builder|null $query */
 $query->where(['status' => $request->input('status')]);
-// TaintedSql: Detected tainted SQL
+// clean
  • Stop reporting TaintedFile on uploaded-file extensions (#1324, #1325). getClientOriginalExtension() is the tail after the final dot of a normalized basename and cannot introduce a path segment, and clientExtension() returns a value from Symfony's MIME registry rather than raw client input. All other taint kinds, including include, are unchanged.
 Storage::putFileAs('uploads', $file, Str::ulid() . '.' . $file->getClientOriginalExtension());
-// TaintedFile: Detected tainted file handling
+// clean
  • Suppress cross-class taint flow through the Dispatchable traits (#1334). Psalm conflated taint nodes from the shared trait bodies, so an argument dispatched to one job appeared to reach an unrelated job's constructor sink. Genuine Bus and Event taint is still reported.

  • Fingerprint the migration schema cache on file contents instead of modification times (#1346). A git clone stamps every file with the checkout time, so the fingerprint changed on every CI run and the cache never hit even when the restored schema was still valid.

Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v3.15.3...v3.15.4

v4.15.4

Extends taint reporting to the call forms Laravel applications actually write — facade statics, response() on its contract, and view() names — and clears the false positives that surfaced alongside it.

Features

  • 🛡️ Report taint sinks on facade static calls, response() contract methods, and view() names (#1318). A facade's surface is [@method](https://github.com/method) pseudo-methods resolved through __callStatic, whose parameters have no docblock to carry a sink, so the static form was silent while the chained form fired.
 Redirect::to($request->input('next'));
-// silent: the facade pseudo-parameter carried no sink
+// TaintedHeader: Detected tainted header
  • Detect undefined relations in model eager-load defaults $with and $withCount (#1321). Supports dotted paths, column selectors, and the $withCount alias grammar; defers when an intermediate related model cannot be resolved.
 class Post extends Model
 {
     protected $with = ['auther'];
-    // silent: eager-load defaults were never validated
+    // UndefinedModelRelation: relation 'auther' is not defined on Post
 }

Fixes

  • Honour [@psalm-taint-escape](https://github.com/psalm-taint-escape) on closure validation rules (#1352), in inline validate() arrays, in FormRequest rules(), and when the closure is the field's whole rule. Previously the only way to assert a rule made a value safe was to extract it into a dedicated Rule class.
 $request->validate([
     'path' => ['required', /** [@psalm-taint-escape](https://github.com/psalm-taint-escape) file */ static fn ($attr, $value, $fail) => /* ... */],
 ]);
-// TaintedFile: a closure body is opaque, so no rule could assert safety
+// clean
  • Fix TaintedSql false positives on where() array values for nullable, template-bounded, and intersection builder receivers (#1338, #1350). Values in the map form are PDO-bound, but a receiver typed Builder|null, [@template](https://github.com/template) T of Builder, or T&Builder declined the strip.
 /** [@param](https://github.com/param) Builder|null $query */
 $query->where(['status' => $request->input('status')]);
-// TaintedSql: Detected tainted SQL
+// clean
  • Stop reporting TaintedFile on uploaded-file extensions (#1324, #1325). getClientOriginalExtension() is the tail after the final dot of a normalized basename and cannot introduce a path segment, and clientExtension() returns a value from Symfony's MIME registry rather than raw client input. All other taint kinds, including include, are unchanged.
 Storage::putFileAs('uploads', $file, Str::ulid() . '.' . $file->getClientOriginalExtension());
-// TaintedFile: Detected tainted file handling
+// clean
  • Suppress cross-class taint flow through the Dispatchable traits (#1334). Psalm conflated taint nodes from the shared trait bodies, so an argument dispatched to one job appeared to reach an unrelated job's constructor sink. Genuine Bus and Event taint is still reported.

  • Fingerprint the migration schema cache on file contents instead of modification times (#1346). A git clone stamps every file with the checkout time, so the fingerprint changed on every CI run and the cache never hit even when the restored schema was still valid.

Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v4.15.3...v4.15.4

v3.15.3

Taint-analysis precision for static where()/whereNot() calls and redirect responses (Psalm 6 line), ported from the paired v4.15.3 release.

Fixes

  1. Fix TaintedSql false positives and a false negative on where()/whereNot() static receivers and nested-condition array values (#1300).
 class ConcreteOverrideModel extends Model {
     /** [@psalm-taint-sink](https://github.com/psalm-taint-sink) sql $column */
     public static function where(mixed $column): void {}
 }

 $term = (string) $request->input('term');
-ConcreteOverrideModel::where([['name', '=', $term]]); // silent (false negative, own sink stripped)
+ConcreteOverrideModel::where([['name', '=', $term]]); // TaintedSql

 Article::where([['name', '=', $request->keyword]]);
-// TaintedSql (false positive) -- mixed value failed the outer position's scalar gate
+// clean -- inner nested-condition value strips unconditionally on a real Model receiver
  1. Fix TaintedSSRF false positive on redirect() and Redirect/ResponseFactory redirect responses. A redirect is a header/open-redirect sink, not a server-side request, so it now reports the correct sink kind (#1313).
-return redirect($request->input('next')); // TaintedSSRF (wrong sink kind)
+return redirect($request->input('next')); // TaintedHeader

Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v3.15.2...v3.15.3

v4.15.3

Taint precision for where() array forms and redirect responses.

Fixes

  1. Fix TaintedSql false positives on where([[...]]) nested-condition arrays: a static receiver spelling (Model::where(...)) skipped the strip entirely, and the inner condition value was scalar-gated so any mixed-typed request value (the common case) kept the sink. Also closes a false negative where a Model subclass with its own concrete where() override had its real sink stripped (#1314).
 $term = (string) $request->input('term');
-User::where([['name', '=', $term]])->get();
-// TaintedSql (false positive) -- static receiver wasn't recognized as PDO-bound

 User::where([['code', '=', $request->keyword]])->first();
-// TaintedSql (false positive) -- mixed-typed value failed the scalar gate
+// clean -- both forms are PDO-bound, matching the instance-receiver behavior
  1. Fix TaintedSSRF incorrectly firing alongside TaintedHeader on redirect() and the Redirector/ResponseFactory redirect family. A redirect sets a Location header for the browser to follow; the server makes no outbound request, so it's an open-redirect/header-injection sink, not SSRF (#1315).
-return redirect($request->url());
-// TaintedSSRF + TaintedHeader (SSRF was mislabelled)
+return redirect($request->url());
+// TaintedHeader only

Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v4.15.2...v4.15.3

v3.15.2

Taint-analysis precision for the query where() family (Psalm 6 line). Resolves the TaintedSql inconsistency from #1300 (the same safe query reported or stayed silent depending on how it was written), and closes two SQL source/sink gaps.

Features 🛡️

  • Source Request::__get() as user input, so $request->term carries taint like $request->input('term') (#1305).
 DB::table('t')->whereRaw((string) $request->input('term')); // TaintedSql
-DB::table('t')->whereRaw((string) $request->term);          // silent
+DB::table('t')->whereRaw((string) $request->term);          // TaintedSql
  • Add SQL taint sinks to whereColumn() / orWhereColumn() on all three identifier positions, which the grammar emits raw (#1308).
-$builder->whereColumn((string) $request->input('c'), '=', 'other'); // silent
+$builder->whereColumn((string) $request->input('c'), '=', 'other'); // TaintedSql
  • Gate the whole-argument where() sql-taint strip on a Laravel builder receiver, so a non-builder where(array $parts) that interpolates raw SQL keeps its report (#1311).

Fixes

  • Fix where() array forms raising a false TaintedSql on PDO-bound value positions; the strip now walks the array literal element-wise and keeps the sink only on raw-identifier positions (#1302, fixes #1300).
 $term = (string) $request->input('term');
-Model::where([['name', 'LIKE', "%{$term}%"]]); // TaintedSql (false positive)
+Model::where([['name', 'LIKE', "%{$term}%"]]); // clean — value is PDO-bound
  • Widen the whereLike-family $value param to mixed, matching where() and the PDO-bound runtime, so idiomatic calls stop reporting false positives (#1312).
-Model::whereLike('name', $request->query('q')); // PossiblyInvalidArgument
+Model::whereLike('name', $request->query('q')); // clean

Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v3.15.1...v3.15.2

v4.15.2

Taint-analysis precision for the query where() family. Resolves the TaintedSql inconsistency from #1300 (the same safe query reported or stayed silent depending on how it was written), and closes two SQL source/sink gaps.

Features 🛡️

  • Source Request::__get() as user input, so $request->term carries taint like $request->input('term') (#1305).
 DB::table('t')->whereRaw((string) $request->input('term')); // TaintedSql
-DB::table('t')->whereRaw((string) $request->term);          // silent
+DB::table('t')->whereRaw((string) $request->term);          // TaintedSql
  • Add SQL taint sinks to whereColumn() / orWhereColumn() on all three identifier positions, which the grammar emits raw (#1308).
-$builder->whereColumn((string) $request->input('c'), '=', 'other'); // silent
+$builder->whereColumn((string) $request->input('c'), '=', 'other'); // TaintedSql
  • Gate the whole-argument where() sql-taint strip on a Laravel builder receiver, so a non-builder where(array $parts) that interpolates raw SQL keeps its report (#1311).

Fixes

  • Fix where() array forms raising a false TaintedSql on PDO-bound value positions; the strip now walks the array literal element-wise and keeps the sink only on raw-identifier positions (#1302, fixes #1300).
 $term = (string) $request->input('term');
-Model::where([['name', 'LIKE', "%{$term}%"]]); // TaintedSql (false positive)
+Model::where([['name', 'LIKE', "%{$term}%"]]); // clean — value is PDO-bound
  • Widen the whereLike-family $value param to mixed, matching where() and the PDO-bound runtime, so idiomatic calls stop reporting false positives (#1312).
-Model::whereLike('name', $request->query('q')); // PossiblyInvalidArgument
+Model::whereLike('name', $request->query('q')); // clean

Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v4.15.1...v4.15.2

v4.15.1

This release improves Eloquent pluck() and builder type narrowing, plus primary-key and factory binding inference.

Features

  • Narrow single-argument 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
  • Fix 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>
  • Auto-bind 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
  • Narrow 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

Fixes

  • Fix custom builder method forwarding on relations (#1267)
 // 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

v3.15.1

This release improves Eloquent pluck() and builder type narrowing, plus primary-key and factory binding inference.

Features

  • Narrow single-argument 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
  • Fix 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>
  • Auto-bind 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
  • Narrow 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

Fixes

  • Fix custom builder method forwarding on relations (#1267)
 // 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

What's Changed

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.

Upgrade impact

  • 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.

Features

  • ⭐️ Add ModelMetadataRegistry, a per-model metadata store warmed once during AfterCodebasePopulated and shared read-only across every Eloquent handler (#1081, #1201)
  • ⭐️ Add the experimental config flag to promote experimental diagnostics to error (#1248)
  • ⭐️ Add 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)
  • ⭐️ Add UnresolvableAppendedModelAttribute: flags a $appends entry with no accessor or cast to actually produce it (#1169)
  • ⭐️ Add 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
  • Infer a precise array shape for attributesToArray()/toArray(), honoring casts, hidden, and appended attributes (#1168, #1184)

Fixes

  • Handle keyless models with nullable primary-key metadata (#1264)
  • Exclude migration files from UnknownModelAttribute (#1265)
  • Stop RelationResolver from autoloading related models, fixing an analysis crash (#1272)

Internal changes

  • Reset stateful handlers between plugin invocations (#1250)
  • Make model metadata warm-up section-isolated, preserving partial metadata after a failure (#1268)
  • Allow psalm-delta on fork PRs with a restore-only cache guard (#1269)

Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v3.14.1592...v3.15.0

v4.15.0

What's Changed

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.

Upgrade impact

  • new UnknownModelAttribute and UndefinedModelRelation report at info by default. Opt into error with <experimental value="true" />.
  • new 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.

Features

  • ⭐️ Add ModelMetadataRegistry, a per-model metadata store warmed once during AfterCodebasePopulated and shared read-only across every Eloquent handler (#1081, #1201)
  • ⭐️ Add the experimental psalm.xml config flag to promote experimental features for early adopters (#1248)
  • Add 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)
  • ⭐️ Add UnresolvableAppendedModelAttribute: flags a $appends entry with no accessor or cast to actually produce it (#1169)
  • ⭐️ Add 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
  • Infer a precise array shape for attributesToArray()/toArray(), honoring casts, hidden, and appended attributes (#1168, #1184)

Fixes

  • Fix whereDate two-argument overload (#1247)

Internal changes

  • Reset stateful handlers between plugin invocations (#1250)
  • Allow psalm-delta on fork PRs with a restore-only cache guard (#1269)

Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v4.14.12...v4.15.0

v3.14.1592

3.14159265... close enough. Here's your π release, the last one, before a big v3.15

Fixes

  • Fix 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

v3.14.12

Narrow several producer and validation return types to their concrete implementations, plus fixes for Cache::driver()->flexible() and monorepo init scanning.

Features

  • Narrow 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
  • Narrow validated integer()/boolean() accessors and rule-bound integer fields from validation rules (#1239, #1237)

Fixes

  • Fix UndefinedInterfaceMethod false positive on Cache::driver()->flexible() (#1233)
  • Fix empty <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

v4.14.12

Narrow several producer and validation return types to their concrete implementations, plus fixes for Cache::driver()->flexible() and monorepo init scanning.

Features

  • Narrow 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
  • Narrow validated integer()/boolean() accessors and rule-bound integer fields from validation rules (#1239, #1237)

Fixes

  • Fix UndefinedInterfaceMethod false positive on Cache::driver()->flexible() (#1233)
  • Fix empty <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

v3.14.11

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:

  • ^12.4 to ^12.14
  • ^13.0 to ^13.3

Features

  • Infer 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>

Fixes

  • Fix false UndefinedMagicMethod on custom Eloquent builder subclass methods called after a fluent chain (#1219)
  • Fix false UndefinedMagicMethod on IndexDefinition modifiers in database migrations (#1220)
  • Fix psalm-laravel init on monorepos: no longer puts packages/ into <ignoreFiles>, which silently disabled analysis of package sources (#1214)
  • Surface swallowed 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

v4.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:

  • ^12.4 to ^12.14
  • ^13.0 to ^13.3

Features

  • Infer 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>

Fixes

  • Surface swallowed 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

v3.14.10

A patch release that focused on removing some false positives.

Fixes

  • Fix false 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

v4.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.

Fixes

  • Fix false TaintedSql on where() calls that pass a column-to-value map (where(['name' => $value])) (#1221)
  • Fix false UndefinedMagicMethod on custom Eloquent builder subclass methods called after a fluent chain (#1219)
  • Fix false UndefinedMagicMethod on IndexDefinition modifiers in database migrations (#1220)
  • Fix 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

v3.14.9

This release improves custom Eloquent builder inference and narrows several stub return types.

Features

  • Infer custom query builders for model instance query methods like newQuery(), newQueryWithoutScopes(), etc. (#1207)

Fixes

  • Narrow getCasts() and getTouchedRelations() return types (#1205)
  • Fix Request::route()/Request::file() default value handling (#1205)

Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v3.14.8...v3.14.9

v4.14.9

This release improves custom Eloquent builder inference and narrows several stub return types.

Features

  • Infer custom query builders for model instance query methods like newQuery(), newQueryWithoutScopes(), etc. (#1207)

Fixes

  • Narrow getCasts() and getTouchedRelations() return types (#1205)
  • Fix Request::route()/Request::file() default value handling (#1205)

Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v4.14.8...v4.14.9

v3.14.8

Patch release fixing Psalm execution on Windows.

Fixes

  • Fix proc_open(): CreateProcess failed warning on Windows by executing psalm through the current PHP binary instead of relying on the shebang (#1189) @HenkPoley

Internal changes

  • Add cross-OS install smoke test (#1197) @alies-dev

Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v3.14.7...v3.14.8

v4.14.8

Patch release fixing Psalm execution on Windows.

Fixes

  • Fix proc_open(): CreateProcess failed warning on Windows by executing psalm through the current PHP binary instead of relying on the shebang (#1189) @HenkPoley

Internal changes

  • Add cross-OS install smoke test (#1197) @alies-dev

Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v4.14.7...v4.14.8

v3.14.7

Fixes caching issue, that makes Psalm complaining about CastsAttributes.

Fixes

  • Fix Psalm cache issue with CastsAttributes: restore user_defined on cast contracts on warm cache (#1188)
  • Fix 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

v4.14.7

Fixes caching issue, that makes Psalm complaining about CastsAttributes.

Internal changes

  • Fix Psalm cache issue with CastsAttributes: Restore user_defined on cast contracts on warm cache (#1188)
  • Refactor: reorganize src/ by domain (retire Providers/ and Util/ grab-bags) (#1185)

Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v4.14.6...v4.14.7

v3.14.6

Fixes

  • Narrow sum/avg/min/max return types on relations and collections instead of widening to mixed (#1183)
  • Fix false UndefinedMethod on forwarded Query\Builder methods in custom builder subclasses (#1145)
  • Guard 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

v4.14.6

Fixes

  • Narrow sum/avg/min/max return types on relations and collections instead of widening to mixed (#1183)
  • Fix false UndefinedMethod on forwarded Query\Builder methods in custom builder subclasses (#1145)
  • Guard 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

v3.14.5

This patch sharpens conditional return types for several Laravel helpers and guards against two crash paths during partial app bootstrap.

Fixes

  • Add conditional return types for Pagination::fragment(), Arr::random(), and Lottery::choose() (#1176)
  • Use a conditional return type for Route::domain() so it narrows $this vs string (#1174)
  • Remap the value type in Paginator::through() to the updated template after the callback (#1173)
  • Guard migrator resolution so analysis no longer crashes when the app bootstraps partially (#1175)
  • Prevent a crash when a container binding resolves to an over-long string (#1179)
  • Forward CLI flags from psalm-laravel analyze through to psalm CLI (#1158)

Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v3.14.4...v3.14.5

v4.14.5

This patch sharpens conditional return types for several Laravel helpers and guards against two crash paths during partial app bootstrap.

Fixes

  • Add conditional return types for Pagination::fragment(), Arr::random(), and Lottery::choose() (#1176)
  • Use a conditional return type for Route::domain() so it narrows $this vs string (#1174)
  • Remap the value type in Paginator::through() to the updated template after the callback (#1173)
  • Guard migrator resolution so analysis no longer crashes when the app bootstraps partially (#1175)
  • Prevent a crash when a container binding resolves to an over-long string (#1179)
  • Forward CLI flags from psalm-laravel analyze through to psalm CLI (#1158)

Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v4.14.4...v4.14.5

v3.14.4

This patch sharpens type inference across collections, the console, and the Date facade, and improves Laravel 11 support by handling Carbon 2.

Features

  • Narrow Command::hasArgument()/hasOption() to literal bool, and fix hasOption() shortcut/negation semantics (#1161)
  • Resolve the configured date class for Date facade static calls (#1157)
  • Infer the named-argument object shape returned by the literal() helper (#1160)
  • Narrow config()->collection() to Collection<key, value> (#1159)
  • Narrow random() return type on Enumerable and LazyCollection (#1152)

Fixes

  • Allow a null option shortcut in Command::getOptions() (#1166)
  • Type the Model::loadCount() constraint closure as Builder (#1164)
  • Allow null values in the __() $replace parameter (#1151)
  • Support Carbon 2 alongside Carbon 3 for better Laravel 11 coverage (#1148)
  • Stop the guard taint warning for SessionGuard::hashPasswordForCookie() on Laravel 11 (#1146)
  • Stop 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

v4.14.4

This patch sharpens type inference across collections, the console, and the Date facade.

Features

  • Narrow Command::hasArgument()/hasOption() to literal bool, and fix hasOption() shortcut/negation semantics (#1161)
  • Resolve the configured date class for Date facade static calls (#1157)
  • Infer the named-argument object shape returned by the literal() helper (#1160)
  • Narrow config()->collection() to Collection<key, value> (#1159)
  • Narrow random() return type on Enumerable and LazyCollection (#1152)

Fixes

  • Allow a null option shortcut in Command::getOptions() (#1166)
  • Type the Model::loadCount() constraint closure as Builder (#1164)
  • Allow null values in the __() $replace parameter (#1151)
  • Stop the guard taint warning for SessionGuard::hashPasswordForCookie() on Laravel 11 (#1146)

Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v4.14.2...v4.14.4

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.

Fixes

  • Resolve concrete-only 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)
  • 🛡️ Stop false 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)
  • Simplify the generated GitHub Actions workflow to a single Psalm job (Psalm 7 runs taint analysis by default) and add CLI-first CI docs (#1132)

Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v3.14.1...v3.14.2

v4.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.

Fixes

  • Resolve concrete-only 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)
  • 🛡️ Stop false 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)
  • Simplify the generated GitHub Actions workflow to a single Psalm job (Psalm 7 runs taint analysis by default) and add CLI-first CI docs (#1132)

Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v4.14.1...v4.14.2

v3.14.1

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:

Features

  • Type $this in Artisan::command() closures so command callbacks resolve $this to the command instance (#1122)

Fixes

  • Scope the Artisan::command() $this override to the callback closure so it no longer leaks to surrounding scope (#1127)
  • Fix false UndefinedMethod on app('encrypter') method calls (#1128)
  • Fix false UndefinedMethod on guard methods reached via auth() narrowing (#1121)
  • Type the no-arg higher-order tap() form as HigherOrderTapProxy<T> (#1112)
  • Accept uppercase orderBy() direction ('ASC'/'DESC') on Laravel <13.8 (#1111)
  • Type Query\Builder::orderBy() direction as asc/desc literals plus SortDirection (#1104)
  • Accept an Expression as the $sql argument to Query\Builder::whereRaw() (#1106)
  • Type Filesystem::hash() return as non-empty-string|false (#1109)
  • Accept a single string column on paginators and find() (#1105)
  • Type firstOrNew/updateOrCreate closure values on Laravel 13.5+ (#1102)
  • Type Connection::cursor() rows as stdClass instead of mixed (#1100)
  • Honor case-insensitive validation taint reads (#1099)

Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v3.14.0...v3.14.1

v4.14.1

Patch release focused on Eloquent and query builder type accuracy, plus follow-up fixes to console closures and facade method resolution.

Features

  • Type $this in Artisan::command() closures so command callbacks resolve $this to the command instance (#1122 #1127)

Fixes

  • Fix false UndefinedMethod on app('encrypter') method calls (#1128)
  • Fix false UndefinedMethod on guard methods reached via auth() narrowing (#1121)
  • Type the no-arg higher-order tap() form as HigherOrderTapProxy<T> (#1112)
  • Accept uppercase orderBy() direction ('ASC'/'DESC') on Laravel <13.8 (#1111)
  • Type Query\Builder::orderBy() direction as asc/desc literals plus SortDirection (#1104)
  • Accept an Expression as the $sql argument to Query\Builder::whereRaw() (#1106)
  • Type Filesystem::hash() return as non-empty-string|false (#1109)
  • Accept a single string column on paginators and find() (#1105)
  • Type firstOrNew/updateOrCreate closure values on Laravel 13.5+ (#1102)
  • Type Connection::cursor() rows as stdClass instead of mixed (#1100)
  • Honor case-insensitive validation taint reads (#1099)

Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v4.14.0...v4.14.1

v3.14.0

What’s Changed

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.

Security 🛡️

  • Detect user-controlled container resolution as unsafe reflection (CWE-470) (#1075)
  • Detect timing-unsafe comparisons of secrets (CWE-208) (#1013)
  • Add html_url taint kind for URL-context XSS distinct from e() (#1014)

Best Practices ⭐️

  • Add opt-in ImplicitQueryBuilderCall rule for direct model query and scope calls. It requires writing User::query()->where(...) instead of User::where(...) (#1068)

Relationships

  • Fix MissingTemplateParam on in-body belongsToMany()/morphToMany() pivot chains (#1090)
  • Fix 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)

Filesystem

  • Narrow Storage::disk() to FilesystemAdapter so temporaryUrl() and url() resolve (#1093)
  • Narrow FilesystemAdapter listing methods to list<string> (#1094)

Forms

  • Narrow FormRequest magic property reads ($request->field calls) (#1022)

Other Features and Fixes

  • Infer app(Foo::class) as Foo when the booted app cannot resolve it (Laravel packages) (#1076)
  • Fix false-positive UndefinedMethod on $app->when()->needs() chains (#1077)
  • Narrow when()/unless() callback return types (#997)
  • Return the concrete paginator from paginate() and preserve the related-model template through relations (#1082)
  • Suppress false PossiblyUnusedMethod on Eloquent trait boot/initialize hooks (#1080)

Internal changes

  • Raise minimum Laravel 12.x version to ^12.4 (Laravel 11.x is still supported on v3.x plugin) (#1089)
  • CI: add a new /psalm-delta workflow to benchmark plugin over real apps and libs (#1079, #1086, #1084) @alies-dev

Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v3.13.2...v3.14.0

v4.14.0

What's Changed

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.

Security 🛡️

  • Detect user-controlled container resolution as unsafe reflection (CWE-470) (#1075)
  • Detect timing-unsafe comparisons of secrets (CWE-208) (#1013)
  • Add html_url taint kind for URL-context XSS distinct from e() (#1014)

Best Practices ⭐️

  • Add opt-in ImplicitQueryBuilderCall rule for direct model query and scope calls. It requires writing User::query()->where(...) instead of User::where(...) (#1068)

Relationships

  • Fix MissingTemplateParam on in-body belongsToMany()/morphToMany() pivot chains (#1090)
  • Fix 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)

Filesystem

  • Narrow Storage::disk() to FilesystemAdapter so temporaryUrl() and url() resolve (#1093)
  • Narrow FilesystemAdapter listing methods to list<string> (#1094)

Forms

  • Narrow FormRequest magic property reads ($request->field calls) (#1022)

Other Features and Fixes

  • Infer app(Foo::class) as Foo when the booted app cannot resolve it (Laravel packages) (#1076)
  • Fix false-positive UndefinedMethod on $app->when()->needs() chains (#1077)
  • Narrow when()/unless() callback return types (#997)
  • Return the concrete paginator from paginate() and preserve the related-model template through relations (#1082)
  • Suppress false PossiblyUnusedMethod on Eloquent trait boot/initialize hooks (#1080)

Internal changes

  • Raise minimum Laravel to ^12.4 (#1089)
  • CI: add a new /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

v3.13.2

What’s Changed

Features

  • Add PublicModelScope and PublicModelAccessor rules for public model scopes and accessors (#1056) @alies-dev
  • Honor value-returning Eloquent scope return types (#1061) @alies-dev

Fixes

Scopes and Eloquent:

  • Report undefined methods on base Builder<Model> from $class::query() (#1073) @alies-dev
  • Resolve higher-order builder where proxies (->orWhere->scope()) (#1066) @alies-dev
  • Suppress unused-method false positives on trait-hosted Eloquent scopes (#1048) @alies-dev
  • Resolve scopes and properties on abstract model base classes (#1058) @alies-dev

Other:

  • Narrow App::make() facade calls to the resolved class (#1072) @alies-dev
  • Fix Carbon dual-purpose method return types on nesbot/carbon >=3.12 (#1060) @alies-dev

Internal changes

  • ci(benchmark): keep Monica beta.5, bump malware-flagged dep instead of downgrading (#1065) @alies-dev

Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v3.13.1...v3.13.2

v4.13.2

What’s Changed

Features

  • Add PublicModelScope and PublicModelAccessor rules for public model scopes and accessors (#1056) @alies-dev
  • Honor value-returning Eloquent scope return types (#1061) @alies-dev

Fixes

Scopes and Eloquent:

  • Report undefined methods on base Builder<Model> from $class::query() (#1073) @alies-dev
  • Resolve higher-order builder where proxies (->orWhere->scope()) (#1066) @alies-dev
  • Suppress unused-method false positives on trait-hosted Eloquent scopes (#1048) @alies-dev
  • Resolve scopes and properties on abstract model base classes (#1058) @alies-dev

Other:

  • Narrow App::make() facade calls to the resolved class (#1072) @alies-dev
  • Fix Carbon dual-purpose method return types on nesbot/carbon >=3.12 (#1060) @alies-dev

Internal changes

  • ci(benchmark): keep Monica beta.5, bump malware-flagged dep instead of downgrading (#1065) @alies-dev

Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v4.13.1...v4.13.2

v3.13.1

What's Changed

Fixes

Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v3.13.0...v3.13.1

v4.13.1

What’s Changed

Fixes

  • Fix trait-hosted #[Scope] detection and scope-vs-QueryBuilder params precedence (#1046) @alies-dev
  • Fix false InvalidArgument on addGlobalScope closures with bare Builder params (#1045) @alies-dev

Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v4.13.0...v4.13.1

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.

Features

  • Resolve instance scope calls on the base Builder to Builder<Model> instead of mixed, type-checking arguments against the scope's declared params (#1032)
  • Resolve self/static scope parameters to the model on custom builders, removing false InvalidArgument and Model&static over-narrowing (#1033)
  • Classify scope calls by PHP dispatch semantics rather than argument shape, so nullable ?Builder, variadic, and non-variable first arguments resolve correctly (#1041)
  • Harden the scope-params hand-off (consume-once) and fix return types when a scope name collides with a real Eloquent\Builder method such as find() (#1042)

Fixes

  • Fix direct scope calls that pass $query explicitly to keep the real method signature, with no left-shifted arguments (#1035)
  • Fix direct scope calls passing $query to use the real return type instead of a fabricated Builder<Model> (#1036)
  • Fix trait-hosted scope self parameters to resolve to the composing class, so a sibling subclass is accepted instead of rejected with a false InvalidArgument (#1043)

Behavior change

  • For a trait composed on an abstract parent, the 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

v4.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.

Features

  • Resolve instance scope calls on the base Builder to Builder<Model> instead of mixed, type-checking arguments against the scope's declared params (#1032)
  • Resolve self/static scope parameters to the model on custom builders, removing false InvalidArgument and Model&static over-narrowing (#1033)
  • Classify scope calls by PHP dispatch semantics rather than argument shape, so nullable ?Builder, variadic, and non-variable first arguments resolve correctly (#1041)
  • Harden the scope-params hand-off (consume-once) and fix return types when a scope name collides with a real Eloquent\Builder method such as find() (#1042)

Fixes

  • Fix direct scope calls that pass $query explicitly to keep the real method signature, with no left-shifted arguments (#1035)
  • Fix direct scope calls passing $query to use the real return type instead of a fabricated Builder<Model> (#1036)
  • Fix trait-hosted scope self parameters to resolve to the composing class, so a sibling subclass is accepted instead of rejected with a false InvalidArgument (#1043)

Behavior change

  • For a trait composed on an abstract parent, the 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

v4.12.4

What's Changed

The same as v4.12.3 (that was initially tagged on a wrong branch)

Features

Internal changes

Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v4.12.2...v4.12.4

v4.12.3

What’s Changed

⚠️ This version was wrongly tagged on 3.x branch, please ignore it.

v3.12.3

Backported features from v4.12.1 and v4.12.2

Features

  • Narrow $this->input() return type inside FormRequest (#1017) @alies-dev
  • Resolve custom CastsAttributes / Castable casts to real types (#1020) @alies-dev
  • Narrow Collection::where() return types (#1019) @alies-dev

Internal changes

Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v3.12.0...v3.12.3

v4.12.2

What’s Changed

Features

  • Resolve custom CastsAttributes / Castable casts to real types (#1020) @alies-dev
  • Narrow Collection::where() return types (#1019) @alies-dev

Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v4.12.1...v4.12.2

v4.12.1

What's Changed

Fixes

Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v4.12.0...v4.12.1

v2.12.3

What's changed:

Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v2.12.2...v2.12.3

v3.12.0

What’s Changed

Features

  • Narrow config() and Repository::get() return types (#1006) @alies-dev
  • Resolve custom Facades in Laravel package source repos (for running on package repos) (#957) @alies-dev

Fixes

  • Specialize Js::from() and Js::encode() taint per call-site (reduce false-positive reports) (#1010) @alies-dev
  • Narrow Request::query() and Request::post() return types (#1009) @alies-dev

Internal changes

  • CI: dedupe push/PR runs and add concurrency cancel (#1008) @alies-dev

Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v3.11.0...v3.12.0

v4.12.0

What’s Changed

Features

  • Narrow config() and Repository::get() return types (#1006) — opt-out by a new resolveConfigReturnTypes config key @alies-dev
  • Resolve custom Facades in Laravel package source repos (for running on package repos) (#957) @alies-dev

Fixes

  • Specialize Js::from() and Js::encode() taint per call-site (reduce false-positive reports) (#1010) @alies-dev
  • Narrow Request::query() and Request::post() return types (#1009) @alies-dev

Internal changes

  • CI: dedupe push/PR runs and add concurrency cancel (#1008) @alies-dev

Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v4.11.0...v4.12.0

v3.11.0

What’s Changed

Features

  • Eloquent improvements:
    • Resolve dynamic where{Column} on Model direct calls (#1001) @alies-dev
    • Narrow Eloquent Builder aggregate (avg(), max(), sum(), etc) returns using a known column type (#1005) @alies-dev
    • Type-check dynamic where{Column} arguments on Eloquent relations (#939) @alies-dev
    • Validate multi-segment dynamic where{Column} method calls (#980) @alies-dev
    • Narrow pluck($value, $key) key type and cover relation chains (#968) @alies-dev
    • Narrow Model::only() return shape from literal keys (#933) @alies-dev
    • Narrow MySQL SET columns to literal union in ModelPropertyHandler (#932) @alies-dev
  • Macroable improvements:
    • Recover macro closure docblocks from vendor packages via AST scan (#994) @alies-dev
    • Use Macroable return type info from docblock Psalm storage (#989) @alies-dev
    • Lock in fluent macro narrowing on closure : static return types (#987) @alies-dev
  • CLI:
    • Add diagnose subcommand for runtime introspection (#959) @alies-dev
    • Add tips for the diagnose command and enhance config created by the init command (#971) @alies-dev
    • Better defaults for psalm-laravel init command @alies-dev
  • Resolve Model::factory()->create() collapse on bare HasFactory (#964) @alies-dev
  • Multi-target facade dispatch for Auth/Cache/Session/Storage/Mail (#907) @alies-dev
  • Narrow auth($name) return to concrete guard class (#981) @alies-dev
  • Narrow Storage::disk() return to Cloud for cloud-driver disks (#982) @alies-dev
  • Resolve Carbon cascade + narrow dual-purpose method returns #922 (#950) @alies-dev
  • Accept variadic strings on Route::middleware facade and RouteRegistrar (#986) @alies-dev
  • Narrow Request::file() via source-level conditional return (#935) @alies-dev

Fixes

  • Dead code mode improvements (PossiblyUnusedMethod):
    • Suppress PossiblyUnusedMethod for legacy scopeXxx() Eloquent methods (#999) @alies-dev
    • Suppress PossiblyUnusedMethod for #[Scope]-attributed Eloquent methods (#998) @alies-dev
  • Anchor config_path() at the project root under Testbench fallback (#949) @alies-dev

Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v3.10.2...v3.11.0

v4.11.0

What’s Changed

Features

  • Eloquent improvements:
    • Resolve dynamic where{Column} on Model direct calls (#1001) @alies-dev
    • Narrow Eloquent Builder aggregate (avg(), max(), sum(), etc) returns using a known column type (#1005) @alies-dev
    • Type-check dynamic where{Column} arguments on Eloquent relations (#939) @alies-dev
    • Validate multi-segment dynamic where{Column} method calls (#980) @alies-dev
    • Narrow pluck($value, $key) key type and cover relation chains (#968) @alies-dev
    • Narrow Model::only() return shape from literal keys (#933) @alies-dev
    • Narrow MySQL SET columns to literal union in ModelPropertyHandler (#932) @alies-dev
  • Macroable improvements:
    • Recover macro closure docblocks from vendor packages via AST scan (#994) @alies-dev
    • Use Macroable return type info from docblock Psalm storage (#989) @alies-dev
    • Lock in fluent macro narrowing on closure : static return types (#987) @alies-dev
  • CLI:
    • Add diagnose subcommand for runtime introspection (#959) @alies-dev
    • Add tips for the diagnose command and enhance config created by the init command (#971) @alies-dev
    • Better defaults for psalm-laravel init command @alies-dev
  • Resolve Model::factory()->create() collapse on bare HasFactory (#964) @alies-dev
  • Multi-target facade dispatch for Auth/Cache/Session/Storage/Mail (#907) @alies-dev
  • Narrow auth($name) return to concrete guard class (#981) @alies-dev
  • Narrow Storage::disk() return to Cloud for cloud-driver disks (#982) @alies-dev
  • Resolve Carbon cascade + narrow dual-purpose method returns #922 (#950) @alies-dev
  • Accept variadic strings on Route::middleware facade and RouteRegistrar (#986) @alies-dev
  • Narrow Request::file() via source-level conditional return (#935) @alies-dev

Fixes

  • Dead code mode improvements (PossiblyUnusedMethod):
    • Suppress PossiblyUnusedMethod for legacy scopeXxx() Eloquent methods (#999) @alies-dev
    • Suppress PossiblyUnusedMethod for #[Scope]-attributed Eloquent methods (#998) @alies-dev
  • Anchor config_path() at the project root under Testbench fallback (#949) @alies-dev

Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v4.10.2...v4.11.0

v3.10.2

What's Changed

Features

Fixes

Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v3.10.1...v3.10.2

v4.10.2

Tighter Request::validated() narrowing, sharper Collection chain inference, and two false-positive fixes in testing + factory flows.

What's Changed

Features

Fixes

Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v4.10.1...v4.10.2

v4.10.1

What's Changed

Features

Fixes

Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v4.10.0...v4.10.1

v3.10.0

What's Changed

Features

Fixes

Internal changes

Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v3.9.2...v3.10.0

v4.10.0

What's Changed

Features

Fixes

Internal changes

Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v4.9.3...v4.10.0

v3.9.3

What's Changed

Improvements

Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v3.9.2...v3.9.3

v4.9.3

What's Changed

Improvements

Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v4.9.2...v4.9.3

v3.9.2

What's Changed

Features & Fixes

Internal changes

New Contributors

Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v3.9.1...v3.9.2

v4.9.2

What's Changed

Features & Fixes

Internal changes

New Contributors

Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v4.9.1...v4.9.2

v3.9.1

What’s Changed

Fixes

  • Fix Psalm crash on AuthManager::__call-forwarded methods (eg auth()->authenticate()) (#856) @alies-dev

Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v3.9.0...v3.9.1

v4.9.1

What’s Changed

Fixes

  • Fix Psalm crash on AuthManager::__call-forwarded methods (eg auth()->authenticate()) (#856) @alies-dev

Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v4.9.0...v4.9.1

v3.9.0

What’s Changed

This release is focused respecting validation rules when to mark input as safe.

Validation & taint analysis

  • Use a more precise taint-escape strategy for validation rules (#819) @alies-dev
  • Extend rule-based taint escape to FormRequest input() / string() / str() accessors (#821) @alies-dev
  • Honour Validation Rule class-level [@psalm-taint-escape](https://github.com/psalm-taint-escape) on custom Rule classes (#826) @alies-dev

Custom Eloquent Builders

  • Preserve fluent return types on custom Eloquent builder subclasses (#845) @alies-dev
  • Fix scopes() chaining on builder contracts (#846) @alies-dev
  • Register custom builder pseudo-method macros (#847) @alies-dev
  • Bind firstOr() callback template across argument positions (#851) @alies-dev

CLI Features

  • New vendor/bin/psalm-laravel CLI with init subcommand for first-time plugin setup (#786) @alies-dev
  • New psalm-laravel add subcommand to scaffold a GitHub Actions security-analysis workflow (#814) @alies-dev

Other type infer Changes

  • Support more Laravel public methods that use variadic parameters: Collection, Session, RedirectResponse, LazyCollection, MessageBag, ServiceProvider(#809, #832) @alies-dev
  • Widen Collection::make / LazyCollection::make / collect() for scalar inputs (#783) @alies-dev
  • Fix InvalidArgument on arrow-function closures in the Builder::where family (#784) @alies-dev
  • Relocate only / except / collect / old to the correct traits (#825) @alies-dev
  • Restate implements / extends in 4 stubs that were wiping reflected metadata (#835, #836) @alies-dev

Internal changes

  • Add StatsHandler to report plugin-level counts under psalm --stats (#817) @alies-dev
  • Include plugin configuration in the bug-report issue body (#781) @alies-dev
v4.9.0

What’s Changed

This release is focused respecting validation rules when to mark input as safe.

Validation & taint analysis

  • Use a more precise taint-escape strategy for validation rules (#819) @alies-dev
  • Extend rule-based taint escape to FormRequest input() / string() / str() accessors (#821) @alies-dev
  • Honour Validation Rule class-level [@psalm-taint-escape](https://github.com/psalm-taint-escape) on custom Rule classes (#826) @alies-dev

Custom Eloquent Builders

  • Preserve fluent return types on custom Eloquent builder subclasses (#845) @alies-dev
  • Fix scopes() chaining on builder contracts (#846) @alies-dev
  • Register custom builder pseudo-method macros (#847) @alies-dev
  • Bind firstOr() callback template across argument positions (#851) @alies-dev

CLI Features

  • New vendor/bin/psalm-laravel CLI with init subcommand for first-time plugin setup (#786) @alies-dev
  • New psalm-laravel add subcommand to scaffold a GitHub Actions security-analysis workflow (#814) @alies-dev

Other type infer Changes

  • Support more Laravel public methods that use variadic parameters: Collection, Session, RedirectResponse, LazyCollection, MessageBag, ServiceProvider(#809, #832) @alies-dev
  • Widen Collection::make / LazyCollection::make / collect() for scalar inputs (#783) @alies-dev
  • Fix InvalidArgument on arrow-function closures in the Builder::where family (#784) @alies-dev
  • Relocate only / except / collect / old to the correct traits (#825) @alies-dev
  • Restate implements / extends in 4 stubs that were wiping reflected metadata (#835, #836) @alies-dev

Internal changes

  • Add StatsHandler to report plugin-level counts under psalm --stats (#817) @alies-dev
  • Include plugin configuration in the bug-report issue body (#781) @alies-dev
v3.8.4

Backport changes from v4.8.2- v4.8.4 releases

What's Changed

  • Narrow AuthManager instance calls, not just the Auth facade (#773) @alies-dev
  • Register Carbon lazy class stubs to prevent MissingDependency errors (#770) @alies-dev
  • Widen Stringable-accepting stubs to reduce ImplicitToStringCast false positives (#775) @alies-dev
  • Tighten filled() to narrow ?string to non-empty-string (#762) @alies-dev
  • fix: narrow app(static::class, ...) and class-string<Foo> arguments (#754) @alies-dev
  • Silence misleading autoloader warning for anonymous Model subclasses (e.g. Laravel Scout) (#769) @alies-dev
  • Stop NoEnvOutsideConfig from firing inside analysed project's config/ (#767) @alies-dev
  • Prevent plugin crashing on invalid Facade aliases by @alies-dev in https://github.com/psalm/psalm-plugin-laravel/pull/746
  • Fix false-positive DocblockTypeContradiction on filled()/blank() guards with nullable strings (#753) @alies-dev

Internal changes

  • Enhance DX with tests (parallel running, optimisations, etc) @alies-dev

Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v3.8.1...v3.8.4

v4.8.4

What’s Changed

This release focuses on fixing minor issues (plugin gaps) found on real projects.

Fixes

  • Narrow AuthManager instance calls, not just the Auth facade (#773) @alies-dev
  • Register Carbon lazy class stubs to prevent MissingDependency errors (#770) @alies-dev
  • Widen Stringable-accepting stubs to reduce ImplicitToStringCast false positives (#775) @alies-dev
  • Tighten filled() to narrow ?string to non-empty-string (#762) @alies-dev
  • Silence misleading autoloader warning for anonymous Model subclasses (e.g. Laravel Scout) (#769) @alies-dev
  • Stop NoEnvOutsideConfig from firing inside analysed project's config/ (#767) @alies-dev

Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v4.8.3...v4.8.4

v4.8.3

What’s Changed

Fixes

  • Fix false-positive DocblockTypeContradiction on filled()/blank() guards with nullable strings (#753) @alies-dev
  • fix: narrow app(static::class, ...) and class-string<Foo> arguments (#754) @alies-dev

Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v4.8.2...v4.8.3

v4.8.2

What’s Changed

Fixes

Internal changes

  • Shorten bug-report title and prevent "vendorsrc" path collapse in IssueUrlGenerator (#747) @alies-dev
  • Tests: Add --SKIPIF-- support to PsalmTest via getSkipReason() (#742) @alies-dev
  • Tests: Run type tests in parallel (use alies-dev/psalm-tester) @alies-dev

Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v4.8.1...v4.8.2

v3.8.1

What's Changed

Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v3.8.0...v3.8.1

v3.8.0

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.

What's Changed

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.

Breaking Changes

  • 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
    

Features

  • Narrow env('KEY', $default) return type based on the default argument — env('KEY', 'val') → string, env('KEY', false) → string|false (#712)
  • Narrow Auth::guard('web') to its concrete class (SessionGuard, TokenGuard) from auth.php config (#711)
  • Narrow Collection::whereNotNull() to remove null from TValue when called without a key (#713)
  • Resolve withCount/withExists/withSum/withMin/withMax/withAvg aggregate accessor properties on Eloquent models without UndefinedMagicPropertyFetch (#715)
  • Validate dispatch() and dispatchIf() arguments against the job/event constructor signature (#726)
  • Infer Carbon (or a custom date class from Date::use()) for now() and today() helpers (#725)
  • Resolve SoftDeletes methods (withTrashed, onlyTrashed, withoutTrashed) on base Builder instances (#727)
  • Type HigherOrderCollectionProxy method call chains precisely — $users->sortByDesc->method() no longer produces InvalidMethodCall (#724)
  • Validate Config::array() and Config::collection() $default argument — scalar fallbacks emit InvalidArgument (#736)
  • Add opt-in <dynamicWhereMethods value="true" /> config to resolve where{Column} calls on relation chains (#714)
  • 🛡️ Mark Http\Client\Response body/header methods (body(), json(), header(), etc.) as taint sources (#676)
  • 🛡️ Add [@psalm-taint-sink](https://github.com/psalm-taint-sink) file to ~30 path-accepting methods in Filesystem, FilesystemAdapter, and LockableFile (#739)

Fixes

  • Fix collect() with no arguments to return Collection<never, never> instead of Collection<array-key, mixed> (#722)
  • Fix Collection::empty() to return static<never, never>, assignable to any typed collection (#679)
  • Narrow Collection::sum() from mixed to int|float (#680)
  • Fix Conditionable::when()/unless() and Tappable::tap() returning mixed on fluent chains — now returns $this (#710)
  • Fix higher-order collection proxy properties ($col->map, $col->each) to carry concrete TKey/TValue types (#720)
  • Narrow Str::replace() return to string when the subject is a string (#719)
  • Add [@psalm-this-out](https://github.com/psalm-this-out) to paginator setCollection() to narrow the type after item replacement (#688)
  • Fix scope methods on Eloquent relation chains — $user->posts()->published()->get() now resolves correctly (#738)
  • 🛡️ Remove false-positive TaintedHtml from Blade view $data parameters — Blade auto-escapes {{ $var }} output (#690)
  • 🛡️ Remove false-positive TaintedSql from PDO-parameterized Builder methods (where, find, having, etc.) (#691)
  • Fix 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

v4.8.1

What’s Changed

  • Enhance AI skill to use compact output mode to save tokens (available from Psalm v7.0.beta-18)
  • Use E_USER_DEPRECATED output type for deprecation warnings

Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v4.8.0...v4.8.1

v4.8.0

Laravel Boost Skill

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.

What’s Changed

Features

  • Add Laravel Boost psalm-security-analysis skill for AI agents (#672) @alies-dev
  • Support HigherOrderCollectionProxy method call chains (#724) @alies-dev
  • Validate Config::array() and Config::collection() default argument type (#736) @alies-dev
  • Resolve scope methods on Eloquent relation chains (#738) @alies-dev
  • 🛡️ Add missing [@psalm-taint-sink](https://github.com/psalm-taint-sink) file methods to Filesystem/FilesystemAdapter stubs (#739) @alies-dev

Fixes

  • Fix: PossiblyUnusedMethod false positives for Eloquent legacy accessor/mutator methods (#732) @alies-dev

Internal changes

  • Simplify 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

v4.7.0

What's Changed

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.

Features

  • Add opt-out dynamic where{Column} method resolution on relation chains (#714)
  • Resolve withCount/withExists/withSum/withMin/withMax/withAvg aggregate accessor properties on Eloquent models (#715)
  • Support SoftDeletes methods resolving on base Builder instances (#727)
  • Update BelongsToMany/MorphToMany stubs to declare 4 template params, restoring pivot types in return values (#716)
  • Infer Carbon for now() and today() helpers (#725)
  • Validate dispatch() arguments against job/event constructor signature (#726)
  • Narrow Auth::guard() return type to the concrete guard class (#711)
  • Narrow Collection::whereNotNull() to remove null from TValue (#713)
  • Narrow env() return type based on the default value argument (#712)

Fixes

  • Fix collect() with no args to return Collection<never, never> (#722)
  • Add [@property-read](https://github.com/property-read) stubs for higher-order collection proxies (#720)
  • Add conditional return type stub for Str::replace() (#719)
  • Stub Conditionable::when()/unless() and Tappable::tap() to fix mixed return on fluent chains (#710)
  • Fix Collection::empty() and Collection::sum() return types (#683)
  • Add [@psalm-this-out](https://github.com/psalm-this-out) to paginator setCollection() (#688)
  • 🛡️ Add [@psalm-taint-escape](https://github.com/psalm-taint-escape) sql to parameterized Builder methods (#691)
  • 🛡️ Remove false-positive TaintedHtml sinks from Blade view data (#690)

Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v4.6.2...v4.7.0

v4.6.2

What’s Changed

Features

  • 🛡️ Add [@psalm-taint-source](https://github.com/psalm-taint-source) input for Http\Client\Response methods (#676) @alies-dev

Improvements

  • Narrow Collection::sum() return type from mixed to int|float (#680) @alies-dev
  • Add Collection::empty() stub with static<never, never> return type (#679) @alies-dev
  • Detect missing views through View facade calls (#668) @alies-dev

Internal changes

  • Support patch-version stub directories (#681) @alies-dev
  • Add taint analysis tests for undertested stub sinks (#675) @alies-dev
  • Refactor test app to Auto Repair Shop domain, reorganize type tests (#667) @alies-dev

Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v4.6.1...v4.6.2

v3.4.0

Backports type inference and taint analysis improvements from Plugin 4.x to Psalm 6 users.

What's Changed

Taint Analysis

  • Cookies — CookieJar make/queue/forever/forget methods flagged as taint-sink header
  • Filesystem — Storage::put(), Storage::prepend(), Storage::append() as path/file sinks
  • HTTP Client — Http::get(), Http::post(), Http::send() as SSRF sinks
  • Sessions — session() helper and Store methods as taint sources (XSS, SQL injection)
  • Views — View::make(), view() helper, View::share() as HTML sinks
  • Mail — Mailable subject/to/from as header sinks, body/line/action as HTML sinks
  • Redis — eval, evalSha, executeRaw as eval sinks
  • Uploaded files — filename, path, contents, MIME type as taint sources
  • Encryption — encrypt()/decrypt() correctly modeled as taint escape/unescape
  • Routing — route parameters as taint sources, redirector as SSRF sink
  • Response — header(), withHeaders(), cookie() as header sinks

Type Inference

Stubs backported from v4.0–v4.6 to reduce false positives:

  • Query Builder — narrowed return types (count → int<0,max>, get → Collection<int, stdClass>, cursor → LazyCollection), added 20+ method stubs (whereNot, having, from, orderBy, etc.)
  • Eloquent Builder — narrowed cursor, pluck, paginators, firstOrCreate; added whereNot, createOrFirst, findSole, chunkMap; @psalm-variadic on with()/without()
  • Model — added Stringable/HasBroadcastChannel implements, public increment/decrement
  • Schema — new stubs for Blueprint, ColumnDefinition, ForeignIdColumnDefinition, ForeignKeyDefinition (fluent migration chains)
  • Auth — new stubs for Authenticatable, SessionGuard, TokenGuard
  • Collection handlers — filter() without callback now removes null/false from TValue; flatten(1)/collapse() preserve TValue

Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v3.3.0...v3.4.0

v4.6.1

What’s Changed

Features

  • Model models variadic arguments: support for static (__callStatic) Model calls (#663) @alies-dev
  • Custom Collections: support returns from Relation method calls (#661) @alies-dev

Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v4.6.0...v4.6.1

v4.6.0

What's Changed

Deep relationship type resolution, Custom query builders, Custom Collections, and smarter validation shapes (thanks to @MDG11).

Custom Query Builders

  • Infer custom query builder types via #[UseEloquentBuilder] attribute and newEloquentBuilder() override (#621) @alies-dev
  • Resolve scope methods on custom query builder instances (#633) @alies-dev
  • Support SoftDeletes trait methods on custom query builders (#632) @alies-dev

Relationships

  • Add MethodForwardingHandler for Relation method forwarding (#642) @alies-dev
  • Resolve morphTo property type from docblock generic annotations (#652) @alies-dev
  • Resolve custom collection types for relation property access (#651) @alies-dev
  • Support #[CollectedBy] attribute for custom Eloquent collections (#623) @alies-dev

Validation

  • Parse dot-notation validation rules into nested array shapes (#625) @MDG11 and @alies-dev

Type Improvements

  • Narrow Collection::flatten() and collapse() return types to preserve TValue (#619) @alies-dev
  • Redeclare Model::increment()/decrement() as public in stub (#618) @alies-dev
  • Skip ModelMakeDiscouraged when model has custom make() method (#616) @alies-dev

Security (Taint Analysis)

  • 🛡️ Add [@psalm-flow](https://github.com/psalm-flow) for Collection get()/first()/pull()/value() default parameter taint propagation (#650) @alies-dev

Internal

  • Replace GNU time with hyperfine + github-action-benchmark (#657) @alies-dev
  • Add CI performance benchmark workflow (#655) @alies-dev

New Contributors

  • @MDG11 made their first contribution in #625 — dot-notation validation rule parsing

Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v4.5.0...v4.6.0

v4.5.0

What's Changed

Three new opt-in rules, expanded taint coverage, and fewer false positives across the board (focus on __() and trans()).

New Rules

  • MissingView: Detect missing Blade view files in view() and View::make() calls (#579) @alies-dev
  • ModelMakeDiscouraged: Detect undefined translation keys in __() and trans() calls (#595) @alies-dev
  • MissingTranslation: Warn against Model::make() in favor of new Model() @alies-dev

Type Improvements

  • Narrow __() and trans() return type to string|array (was mixed) (#592) @alies-dev
  • Narrow __() return to string when the translation key is known to exist @alies-dev
  • Suppress false-positive MissingTemplateParam on HasFactory trait (#517) @alies-dev
  • Skip method forwarding for methods defined directly on Model (#498) @alies-dev
  • Add missing implements clauses to 15 stubs (#615) @alies-dev
  • Fix morphTo stub to bypass $this issue in generics @alies-dev
  • Fix morphToMany/morphedByMany signatures @alies-dev
  • Add [@return](https://github.com/return) static to Stringable stub methods @alies-dev

Security (Taint Analysis)

  • 🛡️ Add [@psalm-taint-source](https://github.com/psalm-taint-source) input for Route parameter methods (#608) @alies-dev
  • 🛡️ Add taint sinks for Redis eval/executeRaw (Lua injection) @alies-dev
  • 🛡️ Add header taint sinks for CookieJar methods @alies-dev
  • 🛡️ Add $path/$domain sinks to Cookie::expire() and forget() @alies-dev
  • 🛡️ Add taint flow tracking through Str::of(), str(), and Stringable @alies-dev
  • 🛡️ Mark Hash::make() and bcrypt() as [@psalm-taint-escape](https://github.com/psalm-taint-escape) system_secret @alies-dev

Benchmark

Tested 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

v3.3.0

Whats' changed

  • feat: update Collection, Model, Builder stubs (backport them from 4.x) @alies-dev

Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v3.2.2...v3.3.0

v3.2.2

What's Changed

Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v3.2.1...v3.2.2

v3.2.1

What's Changed

  • Better type infer for MorphTo relationships @alies-dev

Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v3.2.0...v3.2.1

v3.2.0

What's Changed

Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v3.1.5...v3.2.0

v4.4.0

What’s Changed

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.

Features

  • Improve stub type precision across Eloquent, Collections, Query Builder, and helpers to narrow down types (#583) @alies-dev
  • Add validation-aware type narrowing and taint analysis for FormRequest (#577) @alies-dev
  • 🛡️ Add taint-sink sql annotations for SQL identifiers and table names (#582) @alies-dev
  • 🛡️ Add taint sinks for View\Factory and View\View methods (#580) @alies-dev
  • 🛡️ Add taint escape annotations for Js::from() and Js::encode() (#573) @alies-dev
  • 🛡️ Add taint sources for session data retrieval (Session\Store::get() and other) (#557) @alies-dev
  • 🛡️ Add taint sinks for HTTP client SSRF and redirect methods (#555) @alies-dev
  • 🛡️ Add taint sinks for Mail and Notification classes (#556) @alies-dev

Fixes

  • Remove false-positive taint source from Request::integer() and Request::float() (#575) @alies-dev

Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v4.3.2...v4.4.0

v4.3.2

What’s Changed

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

v4.3.1

What’s Changed

Fixes

  • Accept flexible callable signatures in Attribute::make() (#552)
  • Add [@psalm-taint-escape](https://github.com/psalm-taint-escape) html for e() helper to avoid false negatives (#551)

Internal changes

  • Merge stubs/taintAnalysis/ into stubs/common/ (#553)
  • Add contribution docs for taint analysis

Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v4.3.0...v4.3.1

v4.3.0

What's Changed

This release focuses on migration schema analysis for better Eloquent attribute type inference.

Migration Schema Analysis

  • Support broader Schema call patterns (connection chaining, class constants, custom facades) (#526)
  • Resolve foreignIdFor() column type from referenced model's primary key (#523)
  • Handle Blueprint::datetimes() and fix ulid() default column name (#531)
  • Default to mixed type for unknown Blueprint methods (custom DB types added by macros) (#528)
  • Sort migration files by basename to match Laravel's migrator ordering (#519)
  • Cache parsed migration schema to disk to speed up repeated runs (#524)

Stubs & Type Fixes

  • Fix Collection::map() return type, add Builder::select() and ResponseTrait::cookie() stubs (#548)

Security (Taint Analysis)

  • 🛡️ Add [@psalm-taint-escape](https://github.com/psalm-taint-escape) sql for Connection::escape() (#547)
  • 🛡️ Add taint stubs for UploadedFile and encrypt/decrypt helpers (#546)

Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v4.2.0...v4.3.0

v4.2.0

Highlights

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.).

Features

  • Resolve Eloquent relationship accessors without generic annotations (#502)
  • Resolve static Query\Builder methods and scopes on Model classes (#508)
  • Support SQL schema dumps for Eloquent model attribute discovery (#495)
  • Add stubs for Schema\ColumnDefinition, ForeignIdColumnDefinition, and ForeignKeyDefinition fluent methods (#501)
  • 🛡️ Add taint sink for HtmlString to detect XSS bypass of Blade escaping (#491)
  • 🛡️ Add taint sinks for Storage facade / FilesystemAdapter path traversal detection (#492)

Fixes

  • Process Schema calls in migration helper methods, not just up() (#509)
  • Discover Schema/Blueprint calls inside nested block structures (if/else, try/catch, foreach) (#506)
  • Add missing nullableTimestampsTz() switch case in schema aggregator
  • Narrow count/update/increment/decrement return type to int<0, max> (#499)

Improvements

  • Extract cached hasUserPseudoProperty() helper to reduce redundant storage lookups
  • Add $codebase->progress->debug() to relationship resolution catch blocks for --debug traceability
  • Remove silent constructor catch in findStubFiles() — errors now propagate to the top-level handler

Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v4.1.0...v4.2.0

v4.1.0

What’s Changed

Features

  • feat: infer pluck() value type from model [@property](https://github.com/property) annotations (#488) @alies-dev
  • 🛡️ Add taint sinks for Artisan command injection detection (#489) @alies-dev

Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v4.0.1...v4.1.0

v4.0.1

What’s Changed

  • Taint Analysis: add sinks for sub-query builder methods (#481) @alies-dev
  • Narrow Collection::filter() return type when called without callback (#467) @alies-dev
  • Remove route helper function stub as not needed anymore @alies-dev
  • Remove once helper function stub as not needed anymore @alies-dev

Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v4.0.0...v4.0.1

v4.0.0

The biggest release since the plugin was created. 90% of the codebase was rewritten for stability, performance, and deeper Laravel coverage.

Highlights

  • Up to 50x faster on large projects (new caching layer)
  • Extended security scanning -- 9 taint analysis stubs covering SQL injection, shell injection, file traversal, SSRF, XSS, open redirect, and crypto bypass. Taint analysis now runs automatically in Psalm 7 -- no flags needed, just ./vendor/bin/psalm
  • Compatible with Larastan generics -- relationships, pagination, and Attribute<TGet, TSet> templates all work. Use both tools together: Larastan for types, psalm-plugin-laravel for security
  • Removed barryvdh/laravel-ide-helper dependency -- facades and model properties are now resolved natively by the plugin

New features

  • Custom issue checkers: InvalidConsoleArgumentName, InvalidConsoleOptionName, NoEnvOutsideConfig
  • Model [@property](https://github.com/property) declarations take precedence over migration-discovered properties
  • Enhanced attribute type casting -- AST-based casts() parsing without method execution
  • Scope detection -- both legacy scopeXxx() methods and Laravel 12+ #[Scope] attribute, plus the Scope interface
  • Expanded migration types -- after() closures, Blueprint::rename(), addColumn(), vector columns, and auto-discovery of directories registered via loadMigrationsFrom()

Breaking changes

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>).

Internals

  • Internal code type coverage: 100%
  • Tests run 30x faster
  • PER Coding Style 3.0
  • Better DX for testing and contributing

Upgrade

composer require --dev psalm/plugin-laravel:^4.0 -W

Full migration guide

Security scanning coverage

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

v4.0.0-rc.2

What’s Changed

See v4.0.0 Beta 1 release for full list of major changes

Migration guide

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.

In this RC:

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

4.0 GitHub Discussion

v4.0.0-rc.1

What's Changed

See v4.0.0 Beta 1 release for full list of major changes

Migration guide

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.

In this RC:

RC/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

4.0 GitHub Discussion

v4.0.0-beta.2

What’s Changed (from the previous beta)

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

v4.0.0-beta.1

What's Changed

  • Support Laravel 12–13; drop Laravel 11 (and update other dependencies)
  • Require PHP 8.3+; drop PHP 8.2
  • Require Psalm 7; drop Psalm 6
  • Support Model [@property](https://github.com/property) declarations (take precedence over migration-discovered properties)
  • Compatible with Larastan generics
    • Relationships
    • Pagination
    • Attribute
  • Enhanced Model attribute type casting
  • Enhanced Scope detection (legacy scopeXxx() and #[Scope] attribute)
  • Expanded attribute types inferred from migrations (supports more types inc. vector)
  • Speed up to 50x on big projects (caching)
  • Extended taint-analysis support

Internals

  • Remove barryvdh/laravel-ide-helper dependency — facades and model properties are now resolved natively
  • Run tests faster (30x)
  • Internal code type coverage 100%
  • PER3 coding style
  • Better test coverage

Migration guide

composer 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

v3.1.5

What’s Changed

  • feat: handle dropColumn() with array argument in SchemaAggregator (#448) @alies-dev

Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v3.1.4...v3.1.5

v3.1.4

What’s Changed

  • Fix false-positive ArgumentTypeCoercion for retry() helper @alies-dev

Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v3.1.3...v3.1.4

v3.1.3

What's Changed

SchemaAggregator improvements (#423, #425)

  • Unsigned integer tracking: unsignedBigInteger, increments, foreignId, id, and the ->unsigned() modifier are now recognized, enabling non-negative-int inference for unsigned columns @alies-dev
  • Default values from migrations: ->default() calls in migrations are now parsed and tracked, enabling more accurate type inference for model attributes with defaults @alies-dev
  • Fix: columns silently dropped: non-method-call statements (like if blocks) inside migration closures no longer cause subsequent column definitions to be skipped @alies-dev
  • Fix: foreignIdFor() column name: foreignIdFor(User::class) now correctly resolves to user_id instead of id @alies-dev

Internal

  • Upgrade to PHPUnit 11.5 (#424) @alies-dev
  • CI: add PHP 8.5 and multi-version Laravel installer testing @alies-dev

Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v3.1.2...v3.1.3

v3.1.2

What’s Changed

  • Update barryvdh/laravel-ide-helper dependency @alies-dev

Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v3.1.1...v3.1.2

v3.1.1

What’s Changed

  • Suppress common Laravel issues with full hierarchy support (#400) @alies-dev

Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v3.1.0...v3.1.1

v3.1.0

What’s Changed

Taint Analysis: Security Analysis in Psalm. Example 1, Example 2

  • Add comprehensive Psalm annotations for taint analysis (#418) @alies-dev
  • Fix false-positive ArgumentTypeCoercion for retry() helper (#417) @alies-dev

Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v3.0.5...v3.1.0

v3.0.5

What’s Changed

  • Update dependencies and internal type info (#416) @alies-dev

Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v3.0.4...v3.0.5

v3.0.4

What’s Changed

  • Update stub for dispatch() match upstream Laravel (#407) @saulens22

Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v3.0.3...v3.0.4

v2.12.2

What's Changed

Internal changes:

  • Composer: disableProcessTimeout for a slow test:type @alies-dev
  • Properly initiate GeneratorCommand @alies-dev

Full Changelog: https://github.com/psalm/psalm-plugin-laravel/compare/v2.12.1...v2.12.2

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator
equipc/laravel-email-database-log
adedsec/nova-translated-labels
wazza/laravel-db-encryption
hokny/laravel-generator
vonsogt/backup
jengo/queues
jeremykenedy/laravel-observability
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums