skip to content

In a Livewire component, which types can a public property hold, and why can't a protected property keep state between requests?

level: middleimportance: should knowfreq 45%

answer

  1. state is dehydrated to JSON each response
  2. primitives, BackedEnum, Collection, Carbon, Stringable
  3. a Model is sent as class plus key
  4. Wireable: toLivewire() and fromLivewire()
  5. protected is never sent, never restored

basics

~20 s

Livewire public properties must survive a JSON round trip: primitives and arrays, BackedEnum, Collection, Eloquent models and collections, DateTime/Carbon and Stringable, or a Wireable class. Protected properties are never sent, so each request starts them from their declared default.

solid answer

~40 s

Livewire keeps no component object on the server between requests. After each request it **dehydrates** public properties into the JSON snapshot sent to the browser, and the next request **hydrates** a fresh instance from it. So a public property must be a type Livewire has a synthesizer for: `array`, `string`, `int`, `float`, `bool`, `null`, a `BackedEnum`, `Illuminate\Support\Collection`, an Eloquent `Model` or `Collection` (sent as class and key, re-queried later), `DateTime`/`Carbon` and `Stringable`. Anything else throws "Property type not supported in Livewire" unless the class implements `Livewire\Wireable` with `toLivewire()` and `static fromLivewire()`, or you register a synthesizer. **Protected and private** properties are never sent, so they cannot be tampered with, but they are also not restored: each request starts them from their declared default. They fit constants, not state.

code

php · 16 lines
php
<?php

use App\Enums\StockStatus;
use Livewire\Component;

new class extends Component {
    public int $warehouseId = 1;          // primitive: round-trips
    public ?string $search = null;        // nullable primitive
    public StockStatus $status = StockStatus::Low; // BackedEnum
    public array $pinnedSkus = [];        // array of scalars

    protected int $pageSize = 25;         // never sent, reset each request
};
?>

<div>{{ $search }} / {{ $this->pageSize }}</div>

go deeper

for a junior

Recall that public properties must be simple types or the listed Laravel objects, and that protected ones are hidden and reset every request.

for a middle

Explain dehydration and hydration, what a Model looks like in the snapshot, and how Wireable's toLivewire() and fromLivewire() fit that cycle.

for a senior

Judge which state belongs in public properties, what the snapshot exposes, and when a morph map, Wireable or a synthesizer is the right fix.

for a principal

Weigh the snapshot model's exposure and payload size against keeping state server-side when setting conventions for a large Livewire codebase.

## Why the type list is limited A **Livewire component** does not live on the server between requests. Each response carries a **snapshot**: the component's public properties serialized to JSON (**dehydration**), plus metadata and a checksum. When the browser calls an action or updates a property, it sends the snapshot back, and Livewire builds a brand-new instance and restores the properties from it (**hydration**). A property type is therefore supported only if Livewire knows how to turn it into JSON and back. The code that does that for one type is a **synthesizer**. ## The supported types | Kind | Types | Notes | |---|---|---| | Primitives | `array`, `string`, `int`, `float`, `bool`, `null` | Plain JSON values | | Enums | `BackedEnum` | Stored by backing value; a pure enum without a backing value is not covered | | Collections | `Illuminate\Support\Collection` | Items are dehydrated one by one | | Eloquent | `Model`, `Illuminate\Database\Eloquent\Collection` | Only class and primary key(s) are sent; the rows are queried again on a later request | | Dates | `DateTime`, `Carbon` (any `DateTimeInterface` Livewire maps) | Sent as a formatted string plus type | | Strings | `Illuminate\Support\Stringable` | Round-trips its string value | Livewire also ships synthesizers for its own objects, such as form objects and temporary uploaded files, which are covered with those features. If you assign anything else, for example an instance of your own value class, Livewire throws an exception whose message starts with **"Property type not supported in Livewire for property"**. ## Supporting your own types 1. **Wireable**: implement `Livewire\Wireable` on the class, with `toLivewire()` returning an array or scalar and `public static function fromLivewire($value)` rebuilding the object. 2. **Synthesizers**: for types you do not own or want to support app-wide, register a custom synthesizer class; this is the mechanism package authors use. ```php use Livewire\Wireable; final class StockThreshold implements Wireable { public function __construct(public int $min, public int $max) {} public function toLivewire(): array { return ['min' => $this->min, 'max' => $this->max]; } public static function fromLivewire($value): static { return new static($value['min'], $value['max']); } } ``` ## What a round trip does and does not keep - A **Model** is sent as `{class, key}`. The attributes you loaded, a `select(...)` restriction and eager-loaded relations are not carried; the next request loads the row again by key. The class name appears in the payload unless you register a morph map alias with `Relation::morphMap()`. - A **Collection of closures** or any runtime-only value cannot survive, because JSON has no closure. - Values are **visible** in the page's HTML and can be changed by the browser, so public properties are user input. Protecting a property from changes is a separate feature. ## Protected and private properties Protected and private properties are **never dehydrated**. Two consequences follow: - **Safe from the browser**: users cannot read or modify them, so they suit values you do not want exposed. - **Not persisted**: every request builds a new instance, so a protected property starts again from its declared default. A value you set in `mount()` or in an action is gone on the next click. In Blade, a public property is available as `{{ $threshold }}`; a protected one must be read as `{{ $this->apiLimit }}`. ## A warehouse example A stock widget needs a warehouse id, a threshold and a search term that change as the user works. Those are good public properties: an `int`, an `int` and a `string`. The list of low-stock products is derived data; storing it as a public Eloquent collection works, but it reloads rows by key on later requests and loses any `select` or `with`, so derived lists usually belong in a computed property instead.

  • In a Livewire component, why is a pure enum (one without a backing value) a problem as a public property?
    Livewire's enum synthesizer matches only `BackedEnum`, storing the backing value and rebuilding the case from it. A pure enum has no scalar to store, so no synthesizer matches and Livewire throws the unsupported-type exception. Give the enum a `string` or `int` backing type, or keep it out of public state.
  • When would you write a synthesizer instead of implementing Wireable in Livewire?
    When you cannot or do not want to modify the class, for example a type from a third-party package, or when you want one rule applied to that type across the whole application. `Wireable` is simpler but requires the class itself to implement `toLivewire()` and `fromLivewire()`.

saying these in an interview costs you the question

  • Livewire keeps the component object in server memory between requests
  • Any PHP object can be a public property; Livewire serializes it
  • A public Eloquent model property carries all its attributes and relations to the browser
  • Protected properties keep their values between requests but are hidden
  • Any enum works, backed or not