Build1 publisher2 min readPublished
whereBinary() retires Laravel's raw BINARY workaround one call site at a time
Laravel 13.x adds whereBinary and three variants, compiling on MySQL to = binary ? with the value bound. The fix applies per call site, so the column collation stays as it is and every query you leave alone behaves as before.
The Engineer · Build desk

What happened
- Laravel 13.x adds whereBinary() to the Query Builder, together with whereNotBinary(), orWhereBinary() and orWhereNotBinary(), in Laravel framework PR #61261.
- Many MySQL string comparisons are case-insensitive by default, so a where('name', 'john') lookup can return John, JOHN and JoHn as well, depending on the column's collation.
- Getting an exact comparison previously meant a raw expression, such as whereRaw('name = BINARY ?', ['john']) inside an otherwise ordinary builder chain.
- Eloquent picks the methods up without extra work, because Eloquent queries are built on the same query builder.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
- constraint A codebase already carrying this bug needs every affected query located and edited by hand; the method only reaches the queries you open.
- decision Teams now pick between annotating individual queries and altering the column's collation, and only the second option governs queries written after the decision.
- exposure Any lookup that treats a mixed-case token as a secret has been accepting case variants of it, and the exact comparison arrives only at call sites someone remembers to change.
Call `whereBinary('name', 'john')` and Laravel compiles it to ``where `name` = binary ?``, with the string passed as a bound parameter [4]. The keyword sits on the right of the comparison, in front of the placeholder. `whereNotBinary` flips the operator to `!= binary ?` [5], and `orWhereBinary` appends ``or `name` = binary ?`` to whatever condition came before it [6]. Under that comparison the post gives `john != John` and `John != JOHN` [16].
It is a query builder method, so nothing in the schema moves. Every other `where()` in the codebase keeps comparing under the column's collation [3]. Adoption therefore means finding every affected call site and editing it by hand. Making it opt-in per query is the right call for a framework: changing how `where()` compiles would alter results in every application that configured its collation deliberately. The post is direct about the origin of the behaviour: "This isn't a Laravel problem, it's how MySQL string comparison works with a case-insensitive collation" [9]. If the collation is the cause, the collation is the other place to repair it, and that repair also covers the queries someone writes next year without reading the release notes.
The two older patterns differ in safety. `whereRaw('name = BINARY ?', ['john'])` keeps the value in the binding list [7]. The other form shown in the post, `where('name', '=', DB::raw('BINARY "john"'))`, puts the literal inside the raw expression instead [8].
The `api_keys` lookup in the post is where the default costs more than a few duplicate rows: `whereBinary('key', $providedKey)` [11]. Take a token holding ten cased letters. A case-insensitive comparison treats all 2^10, or 1,024, case variants of that token as equal to the stored value [15]. The row matches for a client that upper-cased the token in transit, and for anyone holding it in the wrong case. That example comes with a boundary: "whereBinary() is about database string comparison, not password security," the post says, and it still sends passwords to Laravel's hashing facilities [12].
Only the MySQL output is documented [14]. A team running SQLite in tests and MySQL in production should check the emitted SQL on both before treating the method as portable. According to the post, the framework's new tests verify the generated SQL [13]. Those tests check the string the builder emits. The behaviour of a live server under your collation is a test you write yourself.
What to watch
- Whether the framework publishes the emitted SQL for the Postgres and SQLite grammars; the post covers MySQL only.
- Whether a binary comparison on an indexed column still uses that index at production row counts.
- Whether teams move the column to a case-sensitive collation instead, making the per-query methods unnecessary.