Collection::whereNotIn

Removes the rows whose value for a key appears in the given array.

Signature
public function whereNotIn(string $key, Arrayable|iterable $values, bool $strict = false): static

Parameters

ParameterTypeDescription
$keystringThe key to read from each item, resolved with data_get so dot paths and object properties work.
$values\Illuminate\Contracts\Support\Arrayable|iterableThe values to exclude. Arrays, collections, generators and any Arrayable are all normalised to an array first.
$strictoptionalbool= falseCompare with === instead of ==. Passing true is equivalent to calling whereNotInStrict().

Return value

staticA new collection of the items that did not match, with the original keys preserved.

whereNotIn is whereIn built on reject instead of filter: it drops every item whose data_get($item, $key) is found in the given list and keeps the rest.

When to reach for it

Exclusion lists — statuses you never want to show, IDs already processed in a previous run, users muted for this request. As with whereIn, the second argument takes any iterable, so $rows->whereNotIn('id', $alreadySent->pluck('id')) works without converting anything first.

The asymmetry that catches people

whereIn and whereNotIn are not complements when a row lacks the key. A missing key resolves to null: whereIn drops that row (null is not in the list) and whereNotIn keeps it (null is not in the list either). Run both over the same collection and the counts can exceed the total. If partial rows should be excluded, filter them out explicitly with whereNotNull first.

What else trips people up

Comparison is loose, and null == 0 in PHP — so whereNotIn('status', [0]) silently removes rows with no status at all. whereNotInStrict() avoids that. Keys are preserved, so chain values() before serializing.

Example

Try it yourself
Loading editor…
Press Run to execute the code.

When to use it

  • Exclude archived and cancelled statuses from a report
  • Skip records already processed in an earlier batch run
  • Remove blocked user IDs from a feed loaded in memory
  • Filter out a set of tags without writing a manual reject callback

Frequently asked questions

How do I exclude a list of values from a Laravel collection?

Use whereNotIn with the key and the values to drop: $rows->whereNotIn('status', ['archived', 'cancelled']). The values argument accepts an array, another collection, a generator or any Arrayable.

Why do whereIn and whereNotIn both keep the same row?

Because that row is missing the key, so data_get resolves it to null — which is neither in your list (so whereIn drops it) nor in your list (so whereNotIn keeps it). Chain whereNotNull first if incomplete rows should be excluded.

Does whereNotIn use strict comparison?

No, it compares loosely by default, so excluding 0 also excludes null and the string '0'. Call whereNotInStrict(), or pass true as the third argument, to compare with ===.

More examples

The counts do not add up

Row 3 lands in $paid purely because its missing tier resolves to null, which is not in ['free']. Here the totals happen to match, but they would not if the list contained a value that loosely equals null. Adding whereNotNull makes the intent explicit and keeps partial rows out of the paid bucket.

Example · php
<?php

$rows = collect([
    ['id' => 1, 'tier' => 'gold'],
    ['id' => 2, 'tier' => 'free'],
    ['id' => 3],                 // no tier at all
]);

$paid = $rows->whereNotIn('tier', ['free']);
$free = $rows->whereIn('tier', ['free']);

echo $paid->pluck('id')->implode(', '), "\n";
echo $free->pluck('id')->implode(', '), "\n";
echo $paid->count() + $free->count(), ' vs ', $rows->count(), "\n";

// Exclude the incomplete rows first if that is what you meant:
echo $rows->whereNotNull('tier')->whereNotIn('tier', ['free'])
          ->pluck('id')->implode(', '), "\n";

// Output:
// 1, 3
// 2
// 3 vs 3
// 1

Loose comparison removing more than you asked

Excluding the value 0 with loose comparison also excludes null and the string '0', leaving a single row. That is rarely the intent when the list came from configuration and the data came from a database that returns strings. whereNotInStrict compares with === and removes exactly the row you named.

Example · php
<?php

$jobs = collect([
    ['id' => 1, 'attempts' => 0],
    ['id' => 2, 'attempts' => null],
    ['id' => 3, 'attempts' => 3],
    ['id' => 4, 'attempts' => '0'],
]);

// null == 0 and '0' == 0, so three rows are removed, not one.
echo $jobs->whereNotIn('attempts', [0])->pluck('id')->implode(', '), "\n";

// Strict comparison removes only the genuine integer 0.
echo $jobs->whereNotInStrict('attempts', [0])->pluck('id')->implode(', '), "\n";

// Output:
// 3
// 2, 3, 4

Skipping already-processed records

$sent is a Collection and is accepted directly, so nothing has to be converted. The whereNotNull guard is deliberate: without it, any subscriber row with a null email would pass whereNotIn on its own merits and be handed to notify(). Needs a booted application with a database.

Example · php
<?php

namespace App\Jobs;

use App\Models\Subscriber;
use App\Models\SentLog;

class SendDigest
{
    public function handle(): void
    {
        $sent = SentLog::whereDate('created_at', today())->pluck('subscriber_id');

        Subscriber::where('active', true)
            ->get()
            ->whereNotIn('id', $sent)
            ->whereNotNull('email')
            ->values()
            ->each(fn (Subscriber $s) => $s->notify(new DailyDigest));
    }
}

Discussion

  • Be the first to comment on this lesson.