CopilotKit

State Rendering

Render your agent's state with custom UI components in real-time.


What is this?#

State rendering lets you build UI that reflects your agent's state in real-time. As your agent progresses through nodes and emits state updates, your frontend renders those changes — showing progress, drafts, or intermediate results.

When should I use this?#

Use state rendering when you want to:

  • Show real-time progress (e.g. "Researching... 2/5 complete")
  • Display drafts that update as the agent works
  • Build dashboards that reflect agent state
  • Render structured output outside of the chat

Implementation#

Run and connect your agent#

You'll need to run your agent and connect it to CopilotKit before proceeding. If you haven't done so already, you can follow the instructions in the Getting Started guide.

If you don't already have an agent, you can use the coagent starter as a starting point as this guide uses it as a starting point.

Define your agent state#

Add properties to your agent state that you want to render in the UI.

agent.py
from copilotkit import CopilotKitState

class AgentState(CopilotKitState):
    searches: list[dict]
agent-js/src/agent.ts
import { StateSchema } from "@langchain/langgraph";
import { CopilotKitStateSchema } from "@copilotkit/sdk-js/langgraph";
import { z } from "zod";

export const AgentStateSchema = new StateSchema({
  searches: z.array(z.object({ query: z.string(), done: z.boolean() })).default(() => []),
  ...CopilotKitStateSchema.fields,
});
export type AgentState = typeof AgentStateSchema.State;

Emit state updates from your agent#

Use copilotkit_emit_state to push intermediate state to the frontend before a node finishes.

agent.py
import asyncio
from copilotkit.langgraph import copilotkit_emit_state 
from langchain_core.runnables import RunnableConfig

async def chat_node(state: AgentState, config: RunnableConfig):
    state["searches"] = [
        {"query": "Initial research", "done": False},
        {"query": "Retrieving sources", "done": False},
        {"query": "Forming an answer", "done": False},
    ]
    await copilotkit_emit_state(config, state) 

    for search in state["searches"]:
        await asyncio.sleep(1)
        search["done"] = True
        await copilotkit_emit_state(config, state) 

    response = await ChatOpenAI(model="gpt-5.4").ainvoke(
        [SystemMessage(content="You are a helpful assistant."), *state["messages"]],
        config,
    )
    return {**state, "messages": response}
agent-js/src/agent.ts
import { copilotkitEmitState } from "@copilotkit/sdk-js/langgraph"; 

async function chat_node(state: AgentState, config: RunnableConfig) {
  state.searches = [
    { query: "Initial research", done: false },
    { query: "Retrieving sources", done: false },
    { query: "Forming an answer", done: false },
  ];
  await copilotkitEmitState(config, state); 

  for (const search of state.searches) {
    await new Promise(resolve => setTimeout(resolve, 1000));
    search.done = true;
    await copilotkitEmitState(config, state); 
  }

  const response = await new ChatOpenAI({ model: "gpt-5.4" }).invoke(
    [new SystemMessage("You are a helpful assistant."), ...state.messages],
    config,
  );
  return { ...state, messages: response };
}

Render state in the UI#

Use the useAgent hook to access agent state anywhere in your app. You can render it in the chat, in dashboards, sidebars, or custom layouts.

app/page.tsx
import { useAgent } from "@copilotkit/react-core/v2"; 

function YourMainContent() {
  const { agent } = useAgent({
    agentId: "sample_agent",
  });

  const searches = agent.state.searches as { query: string; done: boolean }[] ?? [];

  return (
    <div>
      {searches.map((search, index) => (
        <div key={index}>
          {search.done ? "✅" : "⏳"} {search.query}
        </div>
      ))}
    </div>
  );
}

Give it a try!#

You'll see the search items appear and update in real-time as the agent progresses through each step.