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

Uniquewith Validator Laravel Package

felixkiss/uniquewith-validator

Laravel validation rule for uniqueness across multiple columns. The unique_with rule extends Laravel's built-in unique validator, letting you require combinations such as company_id and email to be unique together, with support for ignoring IDs, extra where clauses, and custom column names.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and register. The package is a single validation extension, so getting started is mostly about wiring it into Laravel's validator once:

composer require felixkiss/uniquewith-validator

On Laravel 5.5+ the Felixkiss\UniqueWithValidator\UniqueWithValidatorServiceProvider is auto-discovered. On older versions add it to config/app.php under providers. There is no config file to publish and no migrations — the package only adds a unique_with rule, registered through Validator::extend.

Where to look first. The README is the entire API surface (roughly one page), and the implementation lives in a single class that extends Illuminate\Validation\Validator and defines validateUniqueWith(). If you need to know exactly what query it builds, read that method — it is short. There is nothing else in the package worth exploring.

First use case: uniqueness scoped to an owner/tenant. The canonical scenario is "email must be unique within an account, not globally":

$rules = [
    'account_id' => 'required|exists:accounts,id',
    'email'      => 'required|email|unique_with:users,account_id',
];

$validator = Validator::make($request->all(), $rules);

The rule reads as: the value of the current attribute (email) must not already exist in users where account_id equals the request input account_id. The scoping columns are resolved from the same input payload automatically — that is the whole value proposition versus unique:users plus hand-written where clauses.

Sanity-check it once with a feature test that inserts a duplicate pair and asserts a validation error, because the failure modes (missing input keys, null scoping columns) are silent rather than loud.

Implementation Patterns

FormRequests are the natural home. Because it is a plain string rule, it drops into a rules() method and composes with bail, required, email, etc.:

class StoreUserRequest extends FormRequest
{
    public function rules(): array
    {
        return [
            'account_id' => ['required', 'integer', 'exists:accounts,id'],
            'email'      => ['bail', 'required', 'email', 'unique_with:users,account_id'],
        ];
    }
}

Multiple scoping columns are just additional comma-separated parameters — they are ANDed together:

// name unique per tenant, per parent category
'name' => 'required|unique_with:categories,name,tenant_id,parent_id'

Pin a value instead of reading input. Use the column=value form when the scope is not user-supplied (console commands, seeders, admin actions, queued imports):

'email' => 'required|email|unique_with:users,account_id=' . $account->id

Exclude the current row on update. This is the update-day-to-day need; check the installed README for the exact parameter keyword your version exposes (it has changed across releases), but the shape is:

'email' => ['required', 'email', 'unique_with:users,account_id,ignore=' . $user->id]

Outside HTTP. Since the rule is registered on the global validator, it works identically in Validator::make() inside jobs, importers, and artisan commands — useful when bulk imports need per-tenant dedupe checks before insert.

Know the native alternative. Since Laravel 5.3+, Rule::unique() accepts a where() closure, which covers most of the same ground without a dependency:

use Illuminate\Validation\Rule;

'email' => [
    'required', 'email',
    Rule::unique('users', 'email')
        ->where(fn ($q) => $q->where('account_id', $this->input('account_id'))),
],

Reach for unique_with when you want terse string rules (config-driven rules, arrays of rules, legacy codebases); reach for Rule::unique()->where() when you need ignore(), soft-delete handling, or Laravel 9+/10 support, where the package's release matrix may lag.

Testing pattern. Assert both directions — a duplicate pair fails, and the same value under a different scope passes. The second assertion is the one that catches a mis-ordered parameter list.

Gotchas and Tips

  • Comma-delimited parameters are fragile. unique_with:users,account_id=Some, Inc breaks at the comma. Never interpolate free-text values into the rule string; use the input-lookup form or build a Rule object instead.
  • Missing input key silently becomes IS NULL. If account_id is absent from the payload, the query builder renders where account_id = null as where account_id is null, and you get uniqueness scoped to rows with a null account. Always mark scoping columns required so this can't happen, and order the rules so the scope fails first.
  • Parameter order is easy to invert. The attribute itself is the value column; everything after the table name is a scope column sourced from input. unique_with:users,email,account_id is not the same rule as unique_with:users,account_id on the email field — the former looks for an email input key. Keep a test that proves the scoping.
  • No soft-delete awareness. It queries the table directly, so trashed rows still block reuse unless you scope them out yourself (...,deleted_at=NULL or equivalent) — verify against your installed version, or switch to Rule::unique()->whereNull('deleted_at').
  • Case sensitivity follows the database collation, not PHP. MySQL utf8mb4_unicode_ci treats [email protected] and [email protected] as duplicates; PostgreSQL will not. Behaviour differs between local SQLite and production MySQL — test on the real engine.
  • Validation is not a constraint. Two concurrent requests can both pass validation and then both insert. Add a real composite unique index (account_id, email) in a migration and translate the QueryException (SQLSTATE 23000) into the same friendly validation message.
  • Index for the query you're generating. Each check is an extra SELECT ... LIMIT 1; the composite index that enforces uniqueness is also what keeps this cheap.
  • Debugging. If a rule behaves unexpectedly, log the SQL: wrap the check in DB::listen() or reproduce it with DB::table($table)->where(...)->toSql(). Swapping in a temporary Rule::unique()->where() version is the fastest way to isolate whether the problem is your parameters or the package.
  • Maintenance check before upgrading Laravel. Confirm the installed version's illuminate/support constraint with composer show felixkiss/uniquewith-validator; older tags target older frameworks. On modern Laravel the native rule is usually the lower-risk choice, and unique_with is best treated as a legacy convenience.
  • Extension points are thin by design — if you need custom scope logic (OR groups, whereIn, joins), don't fork the rule; register your own Validator::extend or return a closure-based Rule from rules().
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.
htinlynn/genius-helper
maco-studios/hephaestus
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk
martialbe/flysystem-oss
chondal/3312client
jeroen-g/flickr
opscale-co/nova-genbi
itsjustvita/laravel-retention
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