laravel/framework · error · InvalidArgumentException

Global scope must be an instance of Closure or Scope or be a

Error message

Global scope must be an instance of Closure or Scope or be a class name of a class extending {Scope::class}

What it means

addGlobalScope() accepts exactly four shapes: a Closure, a Scope instance, a Scope subclass name string, or a name string paired with a Closure/Scope implementation. Anything else (random string, non-Scope object, scalar) is rejected with InvalidArgumentException because the scope cannot be applied.

Source

Thrown at src/Illuminate/Database/Eloquent/Concerns/HasGlobalScopes.php:75

     * @param  \Illuminate\Database\Eloquent\Scope|(\Closure(\Illuminate\Database\Eloquent\Builder<static>): mixed)|string  $scope
     * @param  \Illuminate\Database\Eloquent\Scope|(\Closure(\Illuminate\Database\Eloquent\Builder<static>): mixed)|null  $implementation
     * @return mixed
     *
     * @throws \InvalidArgumentException
     */
    public static function addGlobalScope($scope, $implementation = null)
    {
        if (is_string($scope) && ($implementation instanceof Closure || $implementation instanceof Scope)) {
            return static::$globalScopes[static::class][$scope] = $implementation;
        } elseif ($scope instanceof Closure) {
            return static::$globalScopes[static::class][spl_object_hash($scope)] = $scope;
        } elseif ($scope instanceof Scope) {
            return static::$globalScopes[static::class][get_class($scope)] = $scope;
        } elseif (is_string($scope) && class_exists($scope) && is_subclass_of($scope, Scope::class)) {
            return static::$globalScopes[static::class][$scope] = new $scope;
        }

        throw new InvalidArgumentException('Global scope must be an instance of Closure or Scope or be a class name of a class extending '.Scope::class);
    }

    /**
     * Register multiple global scopes on the model.
     *
     * @param  array  $scopes
     * @return void
     */
    public static function addGlobalScopes(array $scopes)
    {
        foreach ($scopes as $key => $scope) {
            if (is_string($key)) {
                static::addGlobalScope($key, $scope);
            } else {
                static::addGlobalScope($scope);
            }
        }
    }

View on GitHub (pinned to bd6b5437e6)

Solutions

  1. Make the scope class implement Illuminate\Database\Eloquent\Scope (with apply(Builder,Model)).
  2. Pass a Closure: Model::addGlobalScope(fn (Builder $b) => $b->where('active', 1)).
  3. If using a string class, ensure it extends/implements Scope and the class is autoloadable.

Example fix

// before
Model::addGlobalScope('active', \App\Scopes\ActiveScope::class);
// ActiveScope does not implement Scope

// after
class ActiveScope implements \Illuminate\Database\Eloquent\Scope
{
    public function apply(Builder $builder, Model $model): void
    {
        $builder->where('active', true);
    }
}
Defensive patterns

Strategy: type-guard

Validate before calling

use Illuminate\Database\Eloquent\Scope;
if ($scope instanceof \Closure || $scope instanceof Scope) {
    Model::addGlobalScope($name, $scope);
} elseif (is_string($scope) && is_subclass_of($scope, Scope::class)) {
    Model::addGlobalScope($scope);
} else {
    throw new \InvalidArgumentException('Invalid global scope: ' . get_debug_type($scope));
}

Type guard

function isValidScope(mixed $scope): bool
{
    return $scope instanceof \Closure
        || $scope instanceof \Illuminate\Database\Eloquent\Scope
        || (is_string($scope) && is_subclass_of($scope, \Illuminate\Database\Eloquent\Scope::class));
}

Try / catch

try {
    Model::addGlobalScope($name, $impl);
} catch (\InvalidArgumentException $e) {
    if (str_contains($e->getMessage(), 'Global scope must be')) {
        report('Invalid scope registration: ' . get_debug_type($impl));
    }
    throw $e;
}

Prevention

When it happens

Trigger: Calling Model::addGlobalScope('active', 'App\Scopes\MissingScope') where the class does not extend Scope or does not exist; passing an object that is not a Scope; passing a string that is not a class with no implementation argument.

Common situations: Custom scope class forgetting to implement Illuminate\Database\Eloquent\Scope; passing a query-builder macro or a callable array (['Obj','method']) which is not a Closure; refactoring that dropped the Scope interface.

Related errors


AI-assisted analysis of laravel/framework@bd6b5437e6 (2026-08-06). Data as JSON: /data/errors/7dfb8084281a172f.json. Report an issue: GitHub.