CopilotKit

Frontend Tools

Let your agent interact with and update your application's UI.


/** * LangGraph TypeScript agent backing the Frontend Tools (In-App Actions) demo. * * The demo is about frontend tools — the agent has no custom backend tools. * CopilotKit forwards the frontend tool schemas to the agent at runtime via * `state.copilotkit.actions`; the agent binds them when invoking the model, * and the handler executes in the browser. */// region: setupimport { RunnableConfig } from "@langchain/core/runnables";import { SystemMessage } from "@langchain/core/messages";import { MemorySaver, START, StateGraph } from "@langchain/langgraph";import { ChatOpenAI } from "@langchain/openai";import {  convertActionsToDynamicStructuredTools,  CopilotKitStateAnnotation,} from "@copilotkit/sdk-js/langgraph";// CopilotKit forwards frontend tools to the agent via// `state.copilotkit.actions`. `CopilotKitStateAnnotation` adds that// channel to your graph's state; `convertActionsToDynamicStructuredTools`// turns the forwarded action schemas into LangChain tools you can bind// at model-invocation time.const AgentStateAnnotation = CopilotKitStateAnnotation;export type AgentState = typeof AgentStateAnnotation.State;const SYSTEM_PROMPT = "You are a helpful, concise assistant.";async function chatNode(state: AgentState, config: RunnableConfig) {  const model = new ChatOpenAI({ temperature: 0, model: "gpt-4o-mini" });  const modelWithTools = model.bindTools!([    ...convertActionsToDynamicStructuredTools(state.copilotkit?.actions ?? []),  ]);  const response = await modelWithTools.invoke(    [new SystemMessage({ content: SYSTEM_PROMPT }), ...state.messages],    config,  );  return { messages: response };}const workflow = new StateGraph(AgentStateAnnotation)  .addNode("chat_node", chatNode)  .addEdge(START, "chat_node")  .addEdge("chat_node", "__end__");const memory = new MemorySaver();export const graph = workflow.compile({  checkpointer: memory,});// endregion

What is this?#

Frontend tools let your agent define and invoke client-side functions that run entirely in the user's browser. Because the handler executes on the frontend, it has direct access to component state, browser APIs, and any third-party UI library the page already uses. That's how an agent can "reach into" the app: update React state, trigger animations, read localStorage, pop a toast, or steer the user's view.

This page covers the "agent drives the UI" shape of frontend tools. The same primitive also powers Generative UI and Human-in-the-loop; see those pages for interaction patterns.

When should I use this?#

Use frontend tools when your agent needs to:

  • Read or modify React component state
  • Access browser APIs like localStorage, sessionStorage, or cookies
  • Trigger UI updates, animations, or transitions
  • Show alerts, toasts, or notifications
  • Interact with third-party frontend libraries
  • Perform anything that requires the user's immediate browser context

How it works in code#

Register a frontend tool with useFrontendTool. Give it a name, a Zod schema for parameters, and a handler. The agent can then call it like any other tool and your frontend runs it in the browser.

page.tsx
import React, { useState } from "react";import {  CopilotKit,  CopilotSidebar,  useFrontendTool,} from "@copilotkit/react-core/v2";import { z } from "zod";import { Background, DEFAULT_BACKGROUND } from "./background";import { useFrontendToolsSuggestions } from "./suggestions";function Chat() {  const [background, setBackground] = useState<string>(DEFAULT_BACKGROUND);  useFrontendTool({    name: "change_background",    description:      "Change the page background. Accepts any valid CSS background value — colors, linear or radial gradients, etc.",    parameters: z.object({      background: z        .string()        .describe("The CSS background value. Prefer gradients."),    }),    handler: async ({ background }) => {      setBackground(background);      return { status: "success" };    },  });

The handler receives the parsed, type-safe parameters and can do anything the browser can: update state, call an API, touch the DOM. Its return value is sent back to the agent as the tool result so the model can reason about what happened.

page.tsx
    handler: async ({ background }) => {      setBackground(background);      return { status: "success" };    },