showcase/shell-docs/src/content/docs/integrations/langgraph/frontend-tools.mdx
<IframeSwitcher id="frontend-actions-example" exampleUrl="https://feature-viewer.copilotkit.ai/langgraph/feature/agentic_chat?sidebar=false&chatDefaultOpen=false" codeUrl="https://feature-viewer.copilotkit.ai/langgraph/feature/agentic_chat?view=code&sidebar=false&codeLayout=tabs" exampleLabel="Demo" codeLabel="Code" height="700px" />
Frontend tools enable you to define client-side functions that your LangGraph agent can invoke, with execution happening entirely in the user's browser. When your agent calls a frontend tool, the logic runs on the client side, giving you direct access to the frontend environment.
This can be utilized to let your agent control the UI, for generative UI, or for Human-in-the-loop interactions.
In this guide, we cover the use of frontend tools driving and interacting with the UI.
Use frontend tools when you need your agent to interact with client-side primitives such as:
<Step>
### Create a frontend tool
First, you'll need to create a frontend tool using the [useFrontendTool](/reference/v2/hooks/useFrontendTool) hook. Here's a simple one to get you started
that says hello to the user.
```tsx title="page.tsx"
import { z } from "zod";
import { useFrontendTool } from "@copilotkit/react-core/v2" // [!code highlight]
export function Page() {
// ...
// [!code highlight:12]
useFrontendTool({
name: "sayHello",
description: "Say hello to the user",
parameters: z.object({
name: z.string().describe("The name of the user to say hello to"),
}),
handler: async ({ name }) => {
alert(`Hello, ${name}!`);
return `Said hello to ${name}!`;
},
});
// ...
}
```
</Step>
<Step>
### Install the CopilotKit SDK
Now, we'll need to modify the agent to access these frontend tools. In your terminal, navigate to your agent's folder and continue from there!
<InstallSDKSnippet/>
</Step>
<Step>
### Inheriting from CopilotKitState
To access the frontend tools provided by CopilotKit, you can inherit from CopilotKitState in your agent's state definition:
<Tabs groupId="language_langgraph_agent" items={['Python', 'TypeScript']} default="Python" persist>
<Tab value="Python">
```python title="agent.py"
from copilotkit import CopilotKitState # [!code highlight]
class YourAgentState(CopilotKitState): # [!code highlight]
your_additional_properties: str
```
</Tab>
<Tab value="TypeScript">
```typescript title="agent-js/src/agent.ts"
import { StateSchema } from "@langchain/langgraph";
import { CopilotKitStateSchema } from "@copilotkit/sdk-js/langgraph"; // [!code highlight]
import { z } from "zod";
export const YourAgentStateSchema = new StateSchema({
yourAdditionalProperty: z.string(),
...CopilotKitStateSchema.fields, // [!code highlight]
});
export type YourAgentState = typeof YourAgentStateSchema.State;
```
</Tab>
</Tabs>
By doing this, your agent's state will include the `copilotkit` property, which contains the frontend tools that can be accessed and invoked.
</Step>
<Step>
### Accessing Frontend Tools
Once your agent's state includes the `copilotkit` property, you can access the frontend tools and utilize them within your agent's logic.
Here's how you can call a frontend tool from your agent:
<FrontEndToolsImpl />
These tools are automatically populated by CopilotKit and are compatible with LangChain's tool call definitions, making it straightforward to integrate them into your agent's workflow.
</Step>
<Step>
### Give it a try!
You've now given your agent the ability to directly call any frontend tools you've defined. These tools will be available to the agent where they can be used as needed.
<video src="https://cdn.copilotkit.ai/docs/copilotkit/images/frontend-actions-demo.mp4" className="rounded-lg shadow-xl" loop playsInline controls autoPlay muted />
</Step>
For simple, low-stakes tools (like toggling a theme or enabling a UI mode), a clear description is usually enough for the agent to discover and call the tool at the right time. However, for tools where correct behavior is critical — such as fetching fresh data before answering, or updating UI state before replying — LangGraph agents may still answer from cached context or skip the tool call entirely.
The root cause is that LLMs weigh tool descriptions against everything else in the context. If the agent has recent data in useAgentContext, it may decide that data is "close enough" and skip the fetch. Stale-data bugs are especially common for domain-specific data pipelines where the agent must call a frontend tool to get up-to-date values.
Recommended pattern: supplement the tool description with an explicit instruction in the agent's system_prompt.
If you find your LangGraph agent answering from stale context instead of calling the tool, add an explicit instruction to system_prompt as shown below.
</Callout>
The following example shows a data-fetch frontend tool paired with a matching system_prompt rule that ensures the agent always calls the tool before answering questions about live data.
<Tabs groupId="language_langgraph_agent" items={['Python', 'TypeScript']} default="Python" persist> <Tab value="Python"> ```python title="agent.py" from langchain.agents import create_agent from copilotkit import CopilotKitMiddleware, CopilotKitState
graph = create_agent( # Works the same for "create_react_agent" or similar options
model="openai:gpt-4o",
tools=[], # backend tools go here
middleware=[CopilotKitMiddleware()],
state_schema=CopilotKitState,
# [!code highlight:7]
# Tell the agent WHEN it must call the frontend tool.
# A short description alone may not be enough — make the rule explicit.
system_prompt=(
"You are a helpful sales assistant.\n\n"
"IMPORTANT: When the user asks about sales data for a specific month or date range, "
"you MUST call the `fetch_sales_by_month_range` frontend tool first to retrieve "
"up-to-date figures. Never answer from memory or previously seen context values."
),
)
```
</Tab>
<Tab value="TypeScript">
```typescript title="agent-js/src/agent.ts"
import { createAgent } from "langchain";
import { copilotkitMiddleware, CopilotKitStateSchema } from "@copilotkit/sdk-js/langgraph";
export const graph = createAgent({ // Works the same for "create_react_agent" or similar options
model: "openai:gpt-4o",
tools: [], // backend tools go here
stateSchema: CopilotKitStateSchema,
middleware: [copilotkitMiddleware],
// [!code highlight:6]
// Tell the agent WHEN it must call the frontend tool.
// A short description alone may not be enough — make the rule explicit.
systemPrompt:
"You are a helpful sales assistant.\n\n" +
"IMPORTANT: When the user asks about sales data for a specific month or date range, " +
"you MUST call the `fetch_sales_by_month_range` frontend tool first to retrieve " +
"up-to-date figures. Never answer from memory or previously seen context values.",
});
```
</Tab>
And register the matching frontend tool on the client:
import { z } from "zod";
import { useFrontendTool } from "@copilotkit/react-core/v2";
useFrontendTool({
name: "fetch_sales_by_month_range",
// [!code highlight:3]
// Keep the description accurate so the model understands the tool's contract,
// but rely on system_prompt to enforce WHEN it is called.
description:
"Fetches sales overview data for the given month range from the live API and updates the dashboard. " +
"Returns a summary of revenue and order counts for the requested period.",
parameters: z.object({
start_month: z.string().describe("Start month in YYYY-MM format"),
end_month: z.string().describe("End month in YYYY-MM format"),
}),
handler: async ({ start_month, end_month }) => {
const data = await fetchSalesOverview({ start_month, end_month });
updateDashboard(data);
return `Fetched sales data from ${start_month} to ${end_month}: revenue=${data.revenue}, orders=${data.orders}`;
},
});
| Situation | Recommended approach |
|---|---|
| Optional UI shortcut (toggle theme, enable mode) | description alone is usually sufficient |
| Mandatory data-fetch that must run before answering | Add an explicit rule to system_prompt |
| Tool that must run in a specific order relative to other tools | Add ordering rules to system_prompt |
| Tool that must NOT be skipped even when context looks stale | Add "never answer from memory" guard to system_prompt |
In addition to letting the LLM decide when to call a frontend tool, you can emit tool calls directly from your LangGraph node code using copilotkit_emit_tool_call. This fires the useFrontendTool handler immediately, without waiting for an LLM decision.
See Connecting Backend Emit APIs to Frontend Hooks for an end-to-end guide covering copilotkit_emit_tool_call, copilotkit_customize_config, and how they relate to useFrontendTool and useComponent.