Laravel 13.27 landed on August 26, 2026 with a mix of security-relevant hardening and new query builder APIs.
The headline change is mask_bindings_in_exception_messages, a per-connection config key. QueryException normally interpolates bindings into its message, so a failing insert puts values like email addresses into getMessage(), which then ends up in the failed_jobs table, log files and APM spans. With the option enabled, the message keeps the ? placeholders while getBindings() still returns the values. The key ships in the framework's own config/database.php, so applications that never published that file can activate it with DB_MASK_BINDINGS=true alone. It defaults to false. Contributed by @LauJosefsen in PR #61326.
The new whereBinary(), orWhereBinary(), whereNotBinary() and orWhereNotBinary() methods bring byte-exact comparisons to the query builder. MySQL's default collations compare case insensitively, so where('name', 'John') also matches john; previously that required whereRaw('name = BINARY ?', ...). MariaDB inherits the MySQL grammar and works too, while Postgres, SQLite and SQL Server throw a RuntimeException, matching how whereLike() behaves there. Contributed by @xiCO2k in PR #61261.
refreshForUpdate() reloads an Eloquent model in place with lockForUpdate() applied. Models are typically resolved before a transaction starts, via route model binding or a job payload, so taking a pessimistic lock previously meant discarding the instance and re-querying by primary key. The lock only lives for the transaction, so the call belongs inside one, and it closes the data race between reading a model and writing it back. Contributed by @stevebauman in PR #61247.
A new Cloud facade answers "are we on Laravel Cloud?" with three methods: Cloud::hosted(), Cloud::usesManagedQueues() and Cloud::queue(). The latter throws a RuntimeException when managed queues are not configured. The facade is deliberately not registered in the default aliases to avoid colliding with existing Cloud classes, and the old bootstrapping class was renamed to CloudBootstrapper. Contributed by @jackbayliss in PR #61275.
Queues gained Queue::totalPendingSize(), totalDelayedSize() and totalReservedSize(), which sum across every queue a connection knows about without decoding job payloads. Implemented for the Redis, database, failover and fake drivers (PR #61231).
Vector distance queries now work on MariaDB. whereVectorSimilarTo(), whereVectorDistanceLessThan(), orderByVectorDistance() and selectVectorDistance() were Postgres-only because the check was an instanceof PostgresConnection with the pgvector <=> operator hardcoded. Both moved to the grammar, and MariaDB 11.7+ compiles to vec_distance_cosine(). Plain MySQL still throws, since VECTOR_DISTANCE() exists only on MySQL HeatWave (PR #61250).
Input handling and validation saw several hardening fixes by @KIKOmanasijev. Request::merge(['*' => 226]) used to wipe the whole input array because keys ran through data_set(), which treats * as a wildcard; Uri::withQuery() had the same bug. Both now use Arr::set() (#61309, #61312). The unknown-field rejection flattened input with Arr::dot(), so a literal "profile.name" key bypassed the check while no rule validated it; input keys now use the same escaping as rule keys (#61313). The in_array and doesnt_contain rules now use strict comparison, ending false matches like "1e0" against "1" (#61319, #61318), and MaintenanceModeBypassCookie::isValid() now checks is_string() instead of isset(), so a crafted array cookie no longer turns a 503 into an unauthenticated 500 (#61314).
Smaller items: Postgres connections accept keepalives, keepalives_idle, keepalives_interval and keepalives_count DSN options, so idle firewall timeouts stop killing long-lived worker connections (#61307). SQS can share cached AWS credentials across processes via a credentials_cache option, enabled by default for Laravel Cloud managed queues (#61321). orWhereKey() and orWhereKeyNot() are back after the 13.26 attempt was reverted for breaking Builder subclasses; the new version delegates through a nested where group (#61242). move() and copy() on read-through disks now stream from the fallback when the primary has not promoted a file yet (#61272). Debounced jobs no longer leave their maxWait timestamp behind (#61281), eager-load constraint closures are static again to break a reference cycle that kept builders alive in queue workers and Octane (#61264), and InteractsWithTestCaseLifecycle::flushState() offers a documented hook for clearing static state between tests (#61288).
Comments
No comments yet — be the first.
Open the discussion
No account or password needed — just enter your e-mail and we’ll send you a one-time sign-in link. First time here? You’re set up automatically.
Your rating will be applied automatically after you sign in.
Check your inbox
We’ve sent a sign-in link to …. Open it on this device — this tab will sign you in automatically.
Nothing arrived? Check your spam folder — and mark the mail as "Not spam" so it lands in your inbox next time.