laravel/framework · error · LogicException

:: must return a relationship instance.

Error message

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

What it means

Thrown by getRelationshipFromMethod() when a method Eloquent treats as a relation returns a value that is neither a Relation instance nor null. Unlike the null case, the method returned something (a string, array, builder, model, etc.), so the generic message is used. Eloquent requires relation methods to return an object implementing Relation.

Solutions

  1. Ensure the method returns the Relation object itself without a terminal execution call (no ->get() / ->first() inside).
  2. Move query scoping into a scope method or apply it at call sites, not in the relation definition.
  3. If you need pre-filtered results, return the Relation and let callers add ->where().

Example fix

// before
public function publishedPosts()
{
    return $this->hasMany(Post::class)->where('published', true)->get();
}

// after
public function publishedPosts()
{
    return $this->hasMany(Post::class)->where('published', true);
}
Defensive patterns

Strategy: type-guard

Validate before calling

$result = $model->{$method}();
if (! $result instanceof \Illuminate\Database\Eloquent\Relations\Relation) {
    throw new \LogicException(static::class . "::{$method} returned " . get_debug_type($result));
}

Type guard

function returnsRelationInstance(string $class, string $method): bool
{
    if (! method_exists($class, $method)) {
        return false;
    }
    $result = (new $class)->{$method}();
    return $result instanceof \Illuminate\Database\Eloquent\Relations\Relation;
}

Prevention

When it happens

Trigger: A relation method that returns a query builder ($this->hasMany(...)->where(...)) where the chain ends on a non-Relation (e.g. returns a Builder or a Model from ->first()), or returns a plain array/scalar. Accessing the relation runs getRelationshipFromMethod() which fails the instanceof Relation check.

Common situations: Chaining a terminal method (->first(), ->get(), ->pluck()) onto the relation definition so it returns results instead of a Relation; returning a custom object that does not extend Relation; a method named like a relation that returns cached data.

Related errors


AI-assisted analysis of laravel/framework@e0f6eb3518 (2026-08-11). Data as JSON: /api/errors/d8d560faab695df9. Report an issue: GitHub.

Appendix: 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 e0f6eb3518)