livewire/livewire · error · Exception

You must define a "hydrate" method

Error message

You must define a "hydrate" method

What it means

The abstract Synth base class marks only match() as abstract - hydrate() is not defined, so a subclass that omits it gets the call routed through Synth::__call(), which throws on the first hydration. This happens on the request after render: dehydration succeeds, then the snapshot comes back and the synth cannot rebuild the value. It is a developer-error guard against half-implemented synthesizers.

Source

Thrown at src/Mechanisms/HandleComponents/Synthesizers/Synth.php:43

    {
        return false;
    }

    function get(&$target, $key) {
        if (is_array($target)) {
            return $target[$key] ?? null;
        }

        return $target->$key;
    }

    function __call($method, $params) {
        if ($method === 'dehydrate') {
            throw new \Exception('You must define a "dehydrate" method');
        }

        if ($method === 'hydrate') {
            throw new \Exception('You must define a "hydrate" method');
        }

        if ($method === 'hydrateFromType') {
            throw new \Exception('You must define a "hydrateFromType" method');
        }

        if ($method === 'get') {
            throw new \Exception('This synth doesn\'t support getting properties: '.get_class($this));
        }

        if ($method === 'set') {
            throw new \Exception('This synth doesn\'t support setting properties: '.get_class($this));
        }

        if ($method === 'unset') {
            throw new \Exception('This synth doesn\'t support unsetting properties: '.get_class($this));
        }

View on GitHub (pinned to 1a3fde34c5)

Solutions

  1. Define hydrate($value, $meta, $hydrateChild) on the synth that reconstructs the target from the dehydrated data
  2. If you only need serialization to the client, switch to a one-way approach (computed property or toArray in dehydrate) instead of a synth
  3. Add a CI test that performs a full round trip (render then update) for components using each custom synth

Example fix

// before - render works, but the next request cannot rebuild the value
class CurrencySynth extends Synth {
    public static $key = 'cur';
    static function match($target) { return $target instanceof Currency; }
    function dehydrate($target, $dehydrateChild) {
        return [['amount' => $target->amount, 'code' => $target->code], []];
    }
}

// after - hydration added
class CurrencySynth extends Synth {
    public static $key = 'cur';
    static function match($target) { return $target instanceof Currency; }
    function dehydrate($target, $dehydrateChild) {
        return [['amount' => $target->amount, 'code' => $target->code], []];
    }
    function hydrate($value, $meta, $hydrateChild) {
        return new Currency($value['amount'], $value['code']);
    }
}
Defensive patterns

Strategy: validation

Validate before calling

foreach ([CurrencySynth::class /* ... */] as $synthClass) {
    foreach (['dehydrate', 'hydrate'] as $method) {
        if (! method_exists($synthClass, $method)) {
            throw new \LogicException("{$synthClass} lacks {$method}() and will throw on the round trip");
        }
    }
}

Type guard

function synthCanHydrate(string $synthClass): bool
{
    return method_exists($synthClass, 'hydrate');
}

Try / catch

try {
    Livewire::test(ComponentUsingCustomSynth::class)
        ->call('someAction'); // triggers hydration from the snapshot
} catch (\Exception $e) {
    if (str_contains($e->getMessage(), 'must define a "hydrate" method')) {
        // add the missing hydrate($value, $meta, $hydrateChild) to the custom synth
    }
    throw $e;
}

Prevention

When it happens

Trigger: A registered custom synth defines $key, match() and dehydrate() but no hydrate(); the component renders fine, and the very next update request (any action or wire:model commit) throws when Livewire tries to reconstitute the property from the snapshot.

Common situations: Synths written and tested only against the initial render path; refactors that drop the method; copying a dehydrate-only example from docs or an AI suggestion.

Related errors


AI-assisted analysis of livewire/livewire@1a3fde34c5 (2026-08-17). Data as JSON: /api/errors/ab8eb4c660b0a335. Report an issue: GitHub.