laravel/framework · error · InvalidArgumentException
The cast object for the {$attribute} attribute must implemen
Error message
The cast object for the {$attribute} attribute must implement Stringable. What it means
mergeCasts()/ensureCastsAreStringValues() accepts object-based casts but they must implement Stringable so the framework can serialize them into the casts array (which holds string cast definitions). Non-Stringable objects cannot be expressed as a cast string.
Source
Thrown at src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php:821
/**
* Ensure that the given casts are strings.
*
* @param array $casts
* @return array
*
* @throws \InvalidArgumentException
*/
protected function ensureCastsAreStringValues($casts)
{
foreach ($casts as $attribute => $cast) {
$casts[$attribute] = match (true) {
is_object($cast) => value(function () use ($cast, $attribute) {
if ($cast instanceof Stringable) {
return (string) $cast;
}
throw new InvalidArgumentException(
"The cast object for the {$attribute} attribute must implement Stringable."
);
}),
is_array($cast) => value(function () use ($cast) {
if (count($cast) === 1) {
return $cast[0];
}
[$cast, $arguments] = [array_shift($cast), $cast];
return $cast.':'.implode(',', $arguments);
}),
default => $cast,
};
}
return $casts;
}View on GitHub (pinned to bd6b5437e6)
Solutions
- Implement the Stringable (or simply __toString()) interface on the cast object so it can render to a cast string.
- Pass the class name string (e.g. MyCaster::class) instead of an instance when registering the cast.
- Implement Castable on the class and use its ::class string in $casts.
Example fix
// before
$model->mergeCasts(['settings' => new PlainObject]);
// after
class SettingsCast implements \Illuminate\Contracts\Database\Eloquent\CastsAttributes, \Stringable
{
public function __toString(): string { return SettingsCast::class; }
}
$model->mergeCasts(['settings' => new SettingsCast]); Defensive patterns
Strategy: type-guard
Validate before calling
foreach ($castsToMerge as $attr => $cast) {
if (is_object($cast) && ! ($cast instanceof \Stringable)) {
throw new \InvalidArgumentException("{$attr} cast object must implement Stringable");
}
}
$model->mergeCasts($castsToMerge); Type guard
function castObjectIsValid(mixed $cast): bool
{
return ! is_object($cast) || $cast instanceof \Stringable;
} Try / catch
try {
$model->mergeCasts($casts);
} catch (\InvalidArgumentException $e) {
if (str_contains($e->getMessage(), 'must implement Stringable')) {
report('Non-Stringable cast object passed');
}
throw $e;
} Prevention
- Prefer passing class-string (Cast::class) for custom casts rather than instances.
- Implement Stringable (__toString) on any caster you intend to register as an object.
- Use Castable classes for parameterized casts.
When it happens
Trigger: Calling $model->mergeCasts(['payload' => $someObject]) or assigning $casts[] = $someObject at runtime where $someObject is a plain stdClass or a class without __toString(). Also from passing a custom caster object that was meant to be a class string.
Common situations: Confusing a CastsAttributes instance (which should be passed as a class string or via ::class) with an object; passing a value object instead of a castable class string; third-party package that returns plain objects as casts.
Related errors
- The provided class must extend [{Collection::class}].
- The provided class must extend [{Collection::class}].
- Value [%s] is not of the expected enum type [%s].
- Unable to create query for collection with mixed types.
- %s::%s must return a relationship instance.
AI-assisted analysis of laravel/framework@bd6b5437e6 (2026-08-06).
Data as JSON: /data/errors/a892d5115c296943.json.
Report an issue: GitHub.