Custom Backend

Custom Runtime

Build a React chat UI for any AI backend with assistant-ui — four runtime patterns covering local state, REST, custom protocols, and external runtimes.

If no framework adapter fits your backend, this section gives you four building blocks. All four are built on one of the two core runtimes; understanding the layering (see architecture) makes the choice clear.

The four paths

PathLayered onWire shapeChoose when
LocalRuntimeCoreYou write a ChatModelAdapter.run functionThe simplest case: you have a fetch call to make, want assistant-ui to handle state
ExternalStoreRuntimeCoreYou provide messages + callbacksYou already have state in redux, zustand, tanstack-query, or anywhere
DataStreamLocalRuntime + protocolYour backend emits the data stream protocolYou want a thin message-stream contract, or you are migrating from AI SDK v4
AssistantTransportExternalStoreRuntime + protocolYour backend streams agent state snapshotsYour agent has rich internal state and you want it surfaced in the UI; or you need bidirectional commands

Decision tree

do you have an AI SDK / LangGraph / Google ADK / A2A backend?
└── yes → use that framework adapter; you do not need this section
└── no → continue

    do you already have message state in redux / zustand / tanstack-query?
    └── yes → ExternalStoreRuntime
    └── no → continue

        do you control the backend wire format?
        └── yes, and i want simple fetch calls → LocalRuntime
        └── yes, and i want to stream a structured agent state → AssistantTransport
        └── no, the backend already speaks data stream protocol → DataStream

What each path gives you

LocalRuntime is the lowest-friction path. You implement one method (run or async *run). Branching, editing, regeneration, multi-thread, and adapter slots all work without extra code.

ExternalStoreRuntime is the inverse: you own the message array and provide callbacks for each interaction. UI features turn on based on which callbacks you provide. Ideal when chat state is a first-class entity in your existing store.

DataStream is LocalRuntime plus a wire protocol. You do not write a ChatModelAdapter; your backend emits a standardized stream of message parts and the runtime consumes it directly.

AssistantTransport is ExternalStoreRuntime plus a state-streaming protocol. Instead of sending message parts, your backend sends snapshots of its full agent state. The UI is a stateless view on top of that state. Supports custom commands beyond the built-in add-message and add-tool-result.

Common building blocks

Regardless of which path you pick, the adapter and thread interfaces work the same way. Wire attachments, history, speech, feedback, suggestions, and multi-thread support through those shared contracts.

Next