swadhin-sikder / laravel-row-in
Adds row value IN and NOT IN support to Laravel's query builder.
Fund package maintenance!
Requires
- php: ^8.3
- illuminate/database: ^12.52.0||^13.0
Requires (Dev)
- larastan/larastan: ^3.12.2
- laravel/agent-detector: ^2.0
- laravel/chisel: ^0.1
- laravel/framework: 12.*
- laravel/pao: ^1.0
- laravel/pint: ^1.29
- laravel/prompts: ^0.3
- orchestra/testbench: ^10.0||^11.0
- pestphp/pest: ^4.6||^5.0
- pestphp/pest-plugin-laravel: ^4.1||^5.0
- pestphp/pest-plugin-type-coverage: ^4.0||^5.0
- phpstan/extension-installer: ^1.4
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-09-19 17:27:23 UTC
README
Laravel Row In
Adds row value IN and NOT IN support to Laravel's query builder — the composite-key equivalent of whereIn, so you can match multiple columns against a list of tuples in a single clause instead of chaining orWhere calls.
// Instead of this... $query->where(function ($query) { $query->where('user_id', 1)->where('role_id', 2); })->orWhere(function ($query) { $query->where('user_id', 3)->where('role_id', 4); }); // ...write this: $query->whereRowIn(['user_id', 'role_id'], [ [1, 2], [3, 4], ]);
Installation
You can install the package via Composer:
composer require swadhin-sikder/laravel-row-in
The package registers its service provider automatically via Laravel's package discovery — no configuration, config files, or migrations to publish. Once installed, whereRowIn, whereNotRowIn, orWhereRowIn, and orWhereNotRowIn are available on every query builder instance.
PhpStorm support is included through an IDE helper file, so these methods are available to autocomplete and navigation after Composer has indexed the package.
Usage
Basic usage
whereRowIn and whereNotRowIn take an array of columns and an array of value tuples, one tuple per row:
use Illuminate\Support\Facades\DB; DB::table('user_roles') ->whereRowIn(['user_id', 'role_id'], [ [1, 2], [3, 4], ]) ->get();
select * from "user_roles" where ("user_id", "role_id") in ((1, 2), (3, 4))
whereNotRowIn compiles the negated form:
DB::table('user_roles') ->whereNotRowIn(['user_id', 'role_id'], [ [1, 2], [3, 4], ]) ->get();
select * from "user_roles" where ("user_id", "role_id") not in ((1, 2), (3, 4))
Any number of columns is supported — two is the common case (composite foreign keys, tenant-scoped IDs), but there's no upper limit:
DB::table('inventory')->whereRowIn(['warehouse_id', 'sku', 'batch'], [ [1, 'ABC-123', 'B01'], [2, 'ABC-123', 'B02'], ]);
Combining with other clauses
Both methods behave like any other where* call and chain normally:
DB::table('users') ->where('active', true) ->whereRowIn(['user_id', 'role_id'], [[1, 2], [3, 4]]) ->orderBy('id') ->get();
or variants
Use orWhereRowIn / orWhereNotRowIn to combine with the preceding clause using or instead of and:
DB::table('users') ->where('is_admin', true) ->orWhereRowIn(['user_id', 'role_id'], [[1, 2], [3, 4]]) ->get();
Subqueries
Pass a Closure, a Builder instance, or anything else Laravel's query builder considers "queryable" instead of an array of values to match against a subquery:
DB::table('users')->whereRowIn( ['user_id', 'role_id'], DB::table('user_roles')->select(['user_id', 'role_id']), );
select * from "users" where ("user_id", "role_id") in ( select "user_id", "role_id" from "user_roles" )
Collections
$values, and any individual row within it, may be an Illuminate\Support\Collection (or anything implementing Arrayable) instead of a plain array:
DB::table('users')->whereRowIn( ['user_id', 'role_id'], collect([[1, 2], [3, 4]]), );
Raw expressions
Individual values within a row can be raw expressions via DB::raw() or Illuminate\Database\Query\Expression:
use Illuminate\Support\Facades\DB; DB::table('users')->whereRowIn(['user_id', 'role_id'], [ [DB::raw('current_user_id()'), 2], ]);
Database support
whereRowIn / whereNotRowIn compile to native row-value IN syntax on MySQL, PostgreSQL, and SQLite:
where ("user_id", "role_id") in ((1, 2), (3, 4))
SQL Server has no native row-value IN syntax, so on that grammar the same call is automatically compiled to an equivalent OR-of-AND expression instead:
where (([user_id] = 1 and [role_id] = 2) or ([user_id] = 3 and [role_id] = 4))
You don't need to do anything differently — the correct SQL is chosen automatically based on the connection's grammar.
Subqueries are rejected for row-in clauses on SQL Server because SQL Server does not support the row-value IN syntax required for this form. An InvalidArgumentException is thrown; use an array of rows with SQL Server, or use a database engine with native row-value IN support for subqueries.
Edge cases
- Empty value lists.
whereRowIn($columns, [])compiles to0 = 1(never matches) andwhereNotRowIn($columns, [])compiles to1 = 1(always matches) — the same convention Laravel's ownwhereIn/whereNotInuse for an empty array, rather than producing invalid SQL. - Row/column count mismatches. Every row must have exactly as many values as there are columns. A row with too many or too few values throws an
InvalidArgumentExceptionnaming the offending row index, before any query bindings are added. - Associative rows. Rows may be associative arrays (e.g.
['user_id' => 1, 'role_id' => 2]) — they're normalized to positional order internally, so the column order you pass is always what's matched against. - Non-array rows. Passing a flat list instead of a list of tuples (e.g.
[1, 2]instead of[[1, 2]]) throws a clearInvalidArgumentExceptionrather than an error deep inside the SQL grammar.
Changelog
Please see CHANGELOG for more information on what has changed recently.
Contributing
Thank you for considering contributing to Laravel Row In! Please review our contributing guide to get started.
Security Vulnerabilities
Please review our security policy on how to report security vulnerabilities.
Credits
License
Laravel Row In is open-sourced software licensed under the MIT license.