The Problem with Laravel's Default MySQL Collation
MySQL string comparisons in Laravel run through the default utf8mb4_unicode_ci collation, which treats several differences as irrelevant: case, accents, trailing whitespace, and some Unicode normalization variants. That is exactly what you want for a display-name search, and exactly what you do not want for a token, a slug, or any value where the bytes themselves are the identity.
This query silently matches A7f3B9, a7f3b9, A7F3B9, and even A7f3B9 (note the trailing space):
DB::table('invites')->where('token', $request->token)->first();
The traditional escape hatch was whereRaw():
DB::table('invites')->whereRaw('token = BINARY ?', [$request->token])->first();
That works, but it steps outside the query builder, making the clause harder to compose with scopes, when() calls, and other conditions.
Introducing whereBinary() in Laravel 13.27
Laravel 13.27 ships four new query-builder methods that wrap the BINARY cast cleanly:
// Exact equality
DB::table('invites')->whereBinary('token', $request->token)->first();
// select * from `invites` where `token` = binary ?
// Negation
DB::table('users')->whereNotBinary('username', $username)->get();
// select * from `users` where `username` != binary ?
// OR variants
DB::table('users')
->where('id', $id)
->orWhereBinary('username', $username)
->get();
// select * from `users` where `id` = ? or `username` = binary ?
Values are still bound as parameters, so parameterization is identical to the whereRaw() approach. The difference is that the clause stays inside the builder and composes naturally with everything else.
Eloquent models work the same way:
$invite = Invite::query()
->whereBinary('token', $request->token)
->where('expires_at', '>', now())
->firstOrFail();
What "Binary" Actually Changes
The BINARY keyword casts the operand to a binary string, forcing a byte-for-byte comparison. Four categories of difference start mattering:
- Case —
Adano longer equalsada. - Accents —
resumeno longer matchesrésumé. - Trailing whitespace —
'ada'and'ada 'are different byte lengths and no longer compare equal. - Unicode normalization —
éas one code point versuseplus a combining accent are distinct byte sequences.
The trailing-whitespace and normalization cases are the ones most likely to cause silent bugs, because they produce false positives rather than obvious failures.
Engine Support and Test-Suite Implications
MySQL and MariaDB support whereBinary(). Every other engine throws:
RuntimeException: This database engine does not support binary comparison operations.
This is intentional. PostgreSQL and SQLite already compare strings case-sensitively by default, so a BINARY cast would either be a no-op or make a promise the driver cannot keep. If your test suite runs on SQLite but production runs on MySQL, any test covering a whereBinary() clause needs a MySQL-backed connection.
The Index Caveat — and How to Handle It
An index built on a _ci column cannot be used to satisfy a BINARY comparison, because the collations differ. On large tables, the recommended pattern is to let the index narrow the result set first, then apply the binary filter:
DB::table('invites')
->where('token', $request->token) // uses the index
->whereBinary('token', $request->token) // filters to byte-exact matches
->first();
The better long-term fix for columns that should always be compared byte-exactly is to declare the right collation in the migration:
$table->string('token')->collation('utf8mb4_bin')->unique();
This also solves something whereBinary() cannot: a unique index on a _ci column will reject Ada when ada already exists regardless of how you query. whereBinary() is a read-side tool; uniqueness enforcement is determined by the column's collation.
Related: Case-Sensitive LIKE
For pattern matching with wildcards, whereLike() already accepts a caseSensitive argument that compiles to like binary on MySQL:
DB::table('users')->whereLike('username', 'ada%', caseSensitive: true);
Use whereBinary() for equality checks and whereLike(..., caseSensitive: true) for pattern matching — the query planner has more optimization options for = than for LIKE.
Key Takeaways
whereBinary()was added in Laravel 13.27 alongsideorWhereBinary(),whereNotBinary(), andorWhereNotBinary().- It replaces
whereRaw('column = BINARY ?', [...])while keeping the query fully composable. - Only MySQL and MariaDB are supported; other engines throw a
RuntimeException. - Binary comparisons bypass column indexes — use the double-condition pattern on large tables or set the column collation to
utf8mb4_bin. whereBinary()is a read-side tool; unique constraint enforcement still depends on the column's collation.
Source: whereBinary(): Case-Sensitive MySQL Queries in Laravel — Laravel News