The root useStream hook exposes always-on projections (values, messages, toolCalls, interrupts, error, isLoading, discovery maps). Anything else — scoped subagent state, message metadata, the submission queue, raw channels, media — is available through the companion selector hooks.
Selector hooks return small reactive handles shaped like { get current(): T }. Read .current in templates, $derived, $effect, or another selector argument so Svelte can track the live value.
Each selector hook opens a ref-counted subscription when the first component mounts it and releases it when the last consumer unmounts. Root calls (no target) are free — they read the already-mounted root projection directly.
All scoped selectors accept a target argument. Valid targets are:
undefined (or omitted) — the root namespace. Free read.SubagentDiscoverySnapshot — as exposed via stream.subagents.values().SubgraphDiscoverySnapshot — as exposed via stream.subgraphs / stream.subgraphsByNode.{ namespace: string[] } (or a raw string[]) — an explicit namespace, useful for custom routing.() => target) — useful when the target comes from $state and should re-bind reactively.Subscriptions open on mount and close when the last consumer for a given (channel, namespace) tuple unmounts. Components that don't render a subagent's content never pay for its wire traffic.
| Hook | Returns | Use for |
|---|---|---|
useValues(stream, target?) |
{ current: StateType } (root) / { current: T \| undefined } (scoped) |
Arbitrary state / scoped snapshot. |
useMessages(stream, target?) |
{ current: BaseMessage[] } |
Message stream, root or scoped. |
useToolCalls(stream, target?) |
{ current: AssembledToolCall[] } |
Tool-call stream, with per-call status. |
useMessageMetadata(stream, msgId) |
{ current: { parentCheckpointId } \| undefined } |
Powers fork / edit flows. See Fork & edit. |
useSubmissionQueue(stream) |
{ entries, size, cancel, clear } |
Reactive client-side submission queue. See Queue. |
useExtension(stream, name, target?) |
{ current: T \| undefined } |
Read a named custom:<name> extension. |
useChannel(stream, channels, target?, options?) |
{ current: Event[] } |
Low-level raw-events escape hatch. |
useChannelEffect(stream, channels, options) |
void |
Per-event side-effect callback. |
useAudio / useImages / useVideo / useFiles |
Media arrays in { current } |
Assembled multimodal streams. See Multimodal. |
useMediaURL(handle) |
{ current: string \| undefined } |
Turns a media handle into an <img/audio/video src>. |
useAudioPlayer(handle, options?) / useVideoPlayer(handle, options?) |
Player handles | Opinionated playback helpers on top of the media hooks. |
<script lang="ts">
import { useMessages, useStream, useToolCalls, useValues } from "@langchain/svelte";
const stream = useStream({ assistantId: "agent", apiUrl: "/api" });
// Root projections — identical to `stream.messages` / `stream.values`.
// These calls are free: no new subscription is opened.
const rootMessages = useMessages(stream);
const rootValues = useValues(stream);
</script>
{#each rootMessages.current as msg (msg.id)}
<Bubble {msg} />
{/each}
{#each [...stream.subagents.values()] as subagent (subagent.namespace.join("/"))}
{@const messages = useMessages(stream, subagent)}
{@const toolCalls = useToolCalls(stream, subagent)}
{@const values = useValues<ResearcherState>(stream, subagent)}
<section>
<header>{subagent.name} — {subagent.status}</header>
{#each messages.current as msg (msg.id)}
<Bubble {msg} />
{/each}
{#if values.current}
<pre>{JSON.stringify(values.current)}</pre>
{/if}
</section>
{/each}
useMessageMetadataReturns { parentCheckpointId } (and undefined while loading). Use it to drive fork / edit UIs:
<script lang="ts">
import { useMessageMetadata } from "@langchain/svelte";
const metadata = useMessageMetadata(stream, () => message.id);
function editFromHere() {
const forkFrom = metadata.current?.parentCheckpointId;
if (!forkFrom) return;
void stream.submit(
{ messages: [{ type: "human", content: "...revised prompt..." }] },
{ forkFrom },
);
}
</script>
<button disabled={!metadata.current?.parentCheckpointId} onclick={editFromHere}>
Edit from here
</button>
See Fork & edit from a checkpoint for the full flow.
useChannelEscape hatch to the raw protocol event stream. Subscribe to one or more channels and get the buffered events as an array:
<script lang="ts">
import { useChannel } from "@langchain/svelte";
const events = useChannel(stream, ["values", "updates"]);
</script>
{#each events.current as event}
<pre>{JSON.stringify(event)}</pre>
{/each}
Pass target (subagent / subgraph / { namespace }) to scope. Useful for bespoke reducers that can't be expressed through useValues / useMessages.
The buffer keeps accumulating across serial runs for the lifetime of the thread, so useChannel is also the selector to use for an event log of a custom channel:
<script lang="ts">
import { useChannel } from "@langchain/svelte";
const statsEvents = useChannel(stream, ["custom:redaction-stats"]);
</script>
useExtensionRead a single custom extension (wire-level custom:<name> channel) as a reactive snapshot:
<script lang="ts">
import { useExtension } from "@langchain/svelte";
const telemetry = useExtension<Telemetry>(stream, "telemetry");
</script>
{#if telemetry.current}
<TelemetryPanel value={telemetry.current} />
{/if}
Runnable example: The
a2uiapp in the streaming cookbook drives a generative UI withuseExtension(stream, "a2ui"); thestreamingpackage'scustom-transformer:*scripts show the server side.
useChannel vs. useExtensionBoth keep receiving events across serial runs on the same thread, but they expose different shapes for a custom:<name> channel:
useExtension — the latest payload only. Use it for current-state panels (progress, score, status).useChannel — the full history of events as a bounded buffer. Use it when you need an event log or want to derive your own running totals.useChannelEffectuseChannelEffect is for raw events you want to react to instead of render. It invokes onEvent once per event and returns nothing, so it never triggers rerenders by itself:
<script lang="ts">
import { useChannelEffect } from "@langchain/svelte";
useChannelEffect(stream, ["lifecycle", "tools"], {
replay: false,
onEvent(event) {
sendAnalytics(event);
},
onError(error) {
logger.error(error);
},
});
</script>
channels, target, and enabled accept getters so reactive $state re-binds the subscription. The subscription is shared (ref-counted) with any matching useChannel, so you only pay for one server subscription per channel set. replay defaults to false (live-only); events buffered before the effect attaches are not re-delivered. Call it from a component script or $effect.root.
useProjection — building your own selectoruseProjection is the low-level primitive every built-in selector is composed from. Reach for it only when you need a custom projection that the built-in hooks don't express. It acquires a ref-counted projection from the stream's channel registry and exposes the projected value through the same .current handle shape:
import { useProjection } from "@langchain/svelte";
import { STREAM_CONTROLLER } from "@langchain/svelte"; // low-level access key
import { valuesProjection } from "@langchain/langgraph-sdk/stream";
function useScoredValues<T>(stream: AnyStream, namespace: readonly string[]) {
const registry = stream[STREAM_CONTROLLER].registry;
return useProjection<T | undefined>(
registry,
() => valuesProjection<T>(namespace, "messages"),
`values|${namespace.join(">")}`,
undefined,
);
}
The first render (before the effect runs) returns initialValue. Subsequent renders read from the acquired store. When the last consumer of a given key unmounts, the underlying server subscription is released automatically. Most apps never need this — prefer useValues / useMessages / useToolCalls / useChannel.
Subscribe to a scoped values stream — the most recent state
Subscribe to a scoped messages stream.
Subscribe to a scoped tools (tool-call) stream. Same target and
Read metadata recorded for a specific message id — today exposes
Subscribe to a custom:<name> stream extension — the most-recent
Side-effect counterpart to useChannel. Instead of returning a
Svelte binding over ChannelRegistry.acquire. Mirrors the