laravel/framework · error · InvalidArgumentException

Model attribute value is an object but does not have a __toS

Error message

Model attribute value is an object but does not have a __toString method.

What it means

InteractsWithDictionary::getDictionaryKey() builds the keys used to match related models when loading relations. Strings/ints/null pass through, objects with __toString or UnitEnum are converted, but any other object is rejected with InvalidArgumentException because it cannot become a stable dictionary key.

Source

Thrown at src/Illuminate/Database/Eloquent/Relations/Concerns/InteractsWithDictionary.php:35

     *
     * @throws \InvalidArgumentException
     */
    protected function getDictionaryKey($attribute)
    {
        if (is_null($attribute) || is_string($attribute) || is_int($attribute)) {
            return $attribute;
        }

        if (is_object($attribute)) {
            if (method_exists($attribute, '__toString')) {
                return $attribute->__toString();
            }

            if ($attribute instanceof UnitEnum) {
                return enum_value($attribute);
            }

            throw new InvalidArgumentException('Model attribute value is an object but does not have a __toString method.');
        }

        return (string) $attribute;
    }
}

View on GitHub (pinned to bd6b5437e6)

Solutions

  1. Ensure key/foreign-key columns cast to scalar string/int (use a string/integer cast, not an object cast).
  2. Implement __toString on the object type if it legitimately represents a key, or use a backed enum (UnitEnum) for enum keys.
  3. Pass the scalar key value instead of an object when matching relations.

Example fix

// before
// a custom cast returns an Amount object for the 'currency' key column
// -> throws during relation load

// after
class Amount { public function __toString(): string { return (string) $this->value; } }
// or cast the key as 'string':
protected $casts = ['currency' => 'string'];
Defensive patterns

Strategy: type-guard

Validate before calling

foreach ($keys as $key) {
    if (is_object($key) && ! method_exists($key, '__toString') && ! ($key instanceof \UnitEnum)) {
        throw new \InvalidArgumentException('Key object lacks __toString: '.get_class($key));
    }
}

Type guard

function isDictionaryKeyable(mixed $key): bool {
    return is_null($key) || is_string($key) || is_int($key)
        || (is_object($key) && (method_exists($key, '__toString') || $key instanceof \UnitEnum));
}

Prevention

When it happens

Trigger: Loading a relation where a key/foreign-key attribute holds an arbitrary object (e.g. a Carbon instance, a DTO, or a stdClass) that has no __toString and is not a UnitEnum; casting a key to an object inadvertently.

Common situations: Custom casts that return an object for a column used as a key; passing a model as a key value; storing non-scalar primary keys; enum-backed keys where the enum cast is misconfigured.

Related errors


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