Live Streams

Live Streams solve a specific, common problem: you want to push updates from your server into open browser pages, from anywhere in your app, and have it work no matter which worker the browser happens to be connected to. A raw SSE handler or a WebSocket Hub only reaches clients on the same worker; Live Streams reach them all.

The idea

A browser subscribes to one or more named channels. Anywhere in your code, on any worker, you publish an event to a channel, and every subscriber to that channel receives it. A cross-worker broker handles the fan-out for you, so you never manage connections directly.

Enabling streams

Turn on Live Streams with use_streams(). Subscriptions deny by default, which is the safe choice, so you either supply an authorizer or explicitly opt into public broadcasts. Start with the public case for something like a shared clock:

use_streams(public: true);

Then publish from anywhere:

push_signals('clock', ['time' => date('H:i:s')]);
push_html('clock', '#messages', '<p>' . e($message) . '</p>');
broadcast('clock', 'custom-event', ['value' => 1]);

Each helper targets the clock channel and reaches every subscribed browser across all workers. push_signals() sends structured data, push_html() swaps HTML into a selector, and broadcast() sends a named custom event.

Private channels

Most real channels are not public. Pass an authorizer that decides which of the requested channels a given request is allowed to join. It returns the subset the subscriber may keep:

use_streams(
	authorizer: function (array $channels, Fast\Http\Request $request): array {
		return array_values(array_filter(
			$channels,
			fn (string $channel): bool => can_subscribe(auth()->user(), $channel),
		));
	},
);

Authorization runs before any SSE headers are committed, so an unauthorized channel is simply never joined. This is how you build per-user notification streams, private rooms, and the like.

Channel rules

To keep things bounded and safe, subscription requests are validated:

  • at most 32 unique channel names per request;
  • each name at most 128 bytes;
  • names may use letters, digits, and the characters ., _, :, and -.

These limits protect the broker from abusive subscription payloads while leaving plenty of room for sensible channel naming like user:42 or room.general.

Delivery guarantees and limits

Live Streams are designed to stay bounded so one busy channel or slow client cannot exhaust a worker. A few numbers worth knowing:

Each client mailbox holds at most 64 events and 1 MiB of serialized frames. Cross-worker events ride an 8 MiB bounded broker frame. Publishing is best-effort: an event larger than the broker limit is dropped with a server-side warning, and this behavior is identical in single-process and multi-worker runtimes, so worker count never changes how your app handles oversize messages.

A note on platforms

Live Streams use the cross-worker broker, which depends on the fork-based shared subsystem model. It is available on Linux, macOS, and BSD, and under WSL2 on Windows. It is not available in the native-Windows multi-worker proc pool; there, use single-process mode or WSL2. See Platform Support for the full picture.

That completes the real-time tour. Next we cover Fast's handy data utilities, starting with the Key-Value Store.