laravel/framework · error · LogicException

%s::%s must return a relationship instance.

Error message

%s::%s must return a relationship instance.

What it means

Same method as 148, but the relation returned a non-null value that is not a Relation instance (e.g. a Builder, Collection, Model, or scalar). Eloquent can only hydrate from a real Relation, so it refuses.

Source

Thrown at src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php:646

     * Get a relationship value from a method.
     *
     * @param  string  $method
     * @return mixed
     *
     * @throws \LogicException
     */
    protected function getRelationshipFromMethod($method)
    {
        $relation = Relation::withConstraintsForNestedRelation(fn () => $this->$method());

        if (! $relation instanceof Relation) {
            if (is_null($relation)) {
                throw new LogicException(sprintf(
                    '%s::%s must return a relationship instance, but "null" was returned. Was the "return" keyword used?', static::class, $method
                ));
            }

            throw new LogicException(sprintf(
                '%s::%s must return a relationship instance.', static::class, $method
            ));
        }

        return tap($relation->getResults(), function ($results) use ($method) {
            $this->setRelation($method, $results);
        });
    }

    /**
     * Determine if a get mutator exists for an attribute.
     *
     * @param  string  $key
     * @return bool
     */
    public function hasGetMutator($key)
    {
        return method_exists($this, 'get'.Str::studly($key).'Attribute');

View on GitHub (pinned to bd6b5437e6)

Solutions

  1. Return only the Relation instance: return $this->hasMany(Comment::class); move ->where() into a scope or caller.
  2. If you need constraints, define them via a closure passed to hasMany/hasOne: $this->hasMany(Comment::class)->where('active', 1).
  3. For computed data, expose a separate accessor method, not a relation method.

Example fix

// before
public function activeComments()
{
    return $this->hasMany(Comment::class)->where('active', 1)->get();
}

// after
public function activeComments()
{
    return $this->hasMany(Comment::class)->where('active', 1);
}
Defensive patterns

Strategy: type-guard

Validate before calling

$r = $model->{$relation}();
if (! $r instanceof \Illuminate\Database\Eloquent\Relations\Relation) {
    throw new \LogicException(get_class($model) . '::' . $relation . ' returned ' . get_debug_type($r));
}

Type guard

function isRelationMethod(object $model, string $method): bool
{
    return $model->{$method}() instanceof \Illuminate\Database\Eloquent\Relations\Relation;
}

Try / catch

try {
    return $model->{$relation};
} catch (\LogicException $e) {
    if (str_contains($e->getMessage(), 'must return a relationship instance')) {
        report(get_class($model) . '::' . $relation . ' returns a non-Relation');
    }
    throw $e;
}

Prevention

When it happens

Trigger: A relation method that returns $this->hasMany(...)->where(...) when chained into a Builder, returns ->get() (a Collection), returns ->first() (a Model), or returns an arbitrary value; then accessing the relation triggers the error.

Common situations: Chaining ->get()/->first() inside the relation method; returning a query-scope result; refactoring that returned the wrong builder type; conditional return paths that return mixed types.

Related errors


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