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
- Ensure key/foreign-key columns cast to scalar string/int (use a string/integer cast, not an object cast).
- Implement __toString on the object type if it legitimately represents a key, or use a backed enum (UnitEnum) for enum keys.
- 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
- Keep key/foreign-key columns cast to scalar string/int.
- Implement __toString or use backed enums for object keys.
- Avoid custom object casts on columns used as relation keys.
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
- The binary codec format is required.
- Unsupported binary codec format [%s]. Allowed formats are: %
- Collection given to whereBelongsTo method may not be empty.
- Collection given to whereAttachedTo method may not be empty.
- Call to undefined method %s::%s()
AI-assisted analysis of laravel/framework@bd6b5437e6 (2026-08-06).
Data as JSON: /data/errors/441115bb3b2dc1f7.json.
Report an issue: GitHub.