Use this file to discover all available pages before exploring further.
CopilotKit provides a full React chat runtime and pairs especially well with LangGraph when you want the agent to return structured UI payloads instead of only plain text. In this pattern, your LangGraph deployment serves both the graph API and a custom CopilotKit endpoint, while the frontend parses assistant messages into dynamic React components.This approach is useful when you want:
a ready-made chat runtime instead of wiring stream.messages yourself
a custom server endpoint that can add provider-specific behavior next to your deployed graph
structured generative UI rendered from a constrained component registry
For CopilotKit-specific APIs, UI patterns, and runtime configuration, see the
CopilotKit docs.
At a high level, CopilotKit sits between your React app and the LangGraph deployment. The frontend sends conversation state to a custom /api/copilotkit route mounted alongside the graph API, that route forwards the request to LangGraph, and the response comes back with both assistant messages and any structured UI payloads your component registry can render.
Deploy the graph as usual using LangSmith or using a LangGraph development server.
Extend the deployment with an HTTP app that mounts a CopilotKit route next to the graph API.
Wrap the frontend in CopilotKit and point it at that custom runtime URL.
Register dynamic UI components and parse assistant responses into those components at render time.
Extend the LangGraph deployment with a custom endpoint
The key idea is that the LangGraph deployment does not only serve graphs. It can also load an HTTP app, which lets you mount extra routes next to the deployment itself.In langgraph.json, point http.app at your custom app entrypoint:
This custom app is the important extension point: it mounts a CopilotKit-aware runtime without replacing the underlying LangGraph deployment.In Python, the equivalent work happens in middleware: normalize the CopilotKit context and forward the output_schema from useAgentContext(...) into the model’s structured output configuration.
src/middleware.py
import jsonfrom collections.abc import Mappingfrom langchain.agents.middleware import before_agent, wrap_model_callfrom langchain.agents.structured_output import ProviderStrategy@wrap_model_callasync def apply_structured_output_schema(request, handler): schema = None runtime = getattr(request, "runtime", None) runtime_context = getattr(runtime, "context", None) if isinstance(runtime_context, Mapping): schema = runtime_context.get("output_schema") if schema is None and isinstance(getattr(request, "state", None), dict): copilot_context = request.state.get("copilotkit", {}).get("context") if isinstance(copilot_context, list): for item in copilot_context: if isinstance(item, dict) and item.get("description") == "output_schema": schema = item.get("value") break if isinstance(schema, str): try: schema = json.loads(schema) except json.JSONDecodeError: schema = None if isinstance(schema, dict): request = request.override( response_format=ProviderStrategy(schema=schema, strict=True), ) return await handler(request)@before_agentdef normalize_context(state, runtime): copilotkit_state = state.get("copilotkit", {}) context = copilotkit_state.get("context") if isinstance(context, list): normalized = [ item.model_dump() if hasattr(item, "model_dump") else item for item in context ] return {"copilotkit": {**copilotkit_state, "context": normalized}} return None
The result is a clean separation of concerns:
LangGraph still owns graph execution and persistence
CopilotKit owns the chat-facing runtime contract
your custom endpoint glues them together inside one deployment
The component registry lives in useChatKit(). This is where you define the set of components the agent is allowed to emit, such as cards, rows, columns, charts, code blocks, and buttons.
import { s } from "@hashbrownai/core";import { exposeComponent, exposeMarkdown, useUiKit } from "@hashbrownai/react";import { Button } from "@/components/ui/button";import { Card } from "@/components/ui/card";import { CodeBlock } from "@/components/ui/code-block";import { Row, Column } from "@/components/ui/layout";import { SimpleChart } from "@/components/ui/simple-chart";export function useChatKit() { return useUiKit({ components: [ exposeMarkdown(), exposeComponent(Card, { name: "card", description: "Card to wrap generative UI content.", children: "any", }), exposeComponent(Row, { name: "row", props: { gap: s.string("Tailwind gap size") as never, }, children: "any", }), exposeComponent(Column, { name: "column", children: "any", }), exposeComponent(SimpleChart, { name: "chart", props: { labels: s.array("Category labels", s.string("A label")), values: s.array("Numeric values", s.number("A value")), }, children: false, }), exposeComponent(CodeBlock, { name: "code_block", props: { code: s.streaming.string("The code to display"), language: s.string("Programming language") as never, }, children: false, }), exposeComponent(Button, { name: "button", children: "text", }), ], });}
This registry becomes the contract between the agent and the UI. The model is not generating arbitrary JSX. It is generating structured data that must validate against the components and props you exposed.