showcase/shell-docs/src/content/reference/hooks/useFrontendTool.mdx
useFrontendTool registers a client-side tool with CopilotKit at component scope. When the agent decides to call the tool, the provided handler function executes in the browser. Optionally, you can supply a render component to display custom UI in the chat showing the tool's execution progress and results.
The hook manages the full registration lifecycle: it warns if a tool with the same name already exists, registers the tool and its render component on mount, and cleans up both registrations on unmount. In v2, parameter schemas are defined using Zod instead of plain parameter arrays.
import { useFrontendTool } from "@copilotkit/react-core/v2";
function useFrontendTool<T extends Record<string, unknown>>(
tool: ReactFrontendTool<T>,
deps?: ReadonlyArray<unknown>,
): void;
<PropertyReference name="handler" type="(args: T, context: FrontendToolHandlerContext) => Promise<unknown>" required
An async function that executes when the agent calls the tool. Receives the validated, typed arguments and the context object:
toolCall (ToolCall) -- the raw tool call metadataagent (AbstractAgent | undefined) -- the agent instance that invoked
the tool; absent when the tool is invoked through WebMCPsignal (AbortSignal | undefined) -- an AbortSignal that is aborted
when the user stops the agent (via stopAgent() or agent.abortRun()).
Long-running handlers can check signal.aborted to exit early.FrontendToolHandlerContext is exported from @copilotkit/core.
</PropertyReference>
<PropertyReference name="render" type="React.ComponentType<{ name: string; args: Partial<T>; status: ToolCallStatus; result: string | undefined }>"
An optional React component rendered in the chat interface to visualize tool
execution. The component receives: - name -- the tool name - args -- the
arguments (partial while streaming, complete once execution starts) - status
-- one of ToolCallStatus.InProgress, ToolCallStatus.Executing, or
ToolCallStatus.Complete - result -- the string result returned by the
handler (only available when status is Complete)
</PropertyReference>
function TodoManager() {
const [todos, setTodos] = useState<string[]>([]);
useFrontendTool(
{
name: "addTodo",
description: "Add a new item to the user's todo list",
parameters: z.object({
text: z.string().describe("The todo item text"),
priority: z.enum(["low", "medium", "high"]).describe("Priority level"),
}),
handler: async ({ text, priority }) => {
setTodos((prev) => [...prev, text]);
return `Added "${text}" with ${priority} priority`;
},
},
[],
);
return (
<ul>
{todos.map((t, i) => (
<li key={i}>{t}</li>
))}
</ul>
);
}
function WeatherWidget() {
useFrontendTool(
{
name: "getWeather",
description: "Fetch and display weather information for a city",
parameters: z.object({
city: z.string().describe("City name"),
units: z.enum(["celsius", "fahrenheit"]).default("celsius"),
}),
handler: async ({ city, units }, { signal }) => {
const response = await fetch(
`/api/weather?city=${city}&units=${units}`,
{ signal },
);
const data = await response.json();
return JSON.stringify(data);
},
render: ({ args, status, result }) => {
if (status === ToolCallStatus.InProgress) {
return (
<div className="animate-pulse">
Fetching weather for {args.city}...
</div>
);
}
if (status === ToolCallStatus.Complete && result) {
const data = JSON.parse(result);
return (
<div className="p-4 border rounded">
<h3>{data.city}</h3>
<p>
{data.temperature}° {data.units}
</p>
<p>{data.conditions}</p>
</div>
);
}
return null;
},
},
[],
);
return null;
}
function AdminPanel({ isAdmin }: { isAdmin: boolean }) {
useFrontendTool(
{
name: "deleteUser",
description: "Delete a user account by ID (admin only)",
parameters: z.object({
userId: z.string().describe("The ID of the user to delete"),
}),
handler: async ({ userId }) => {
await fetch(`/api/users/${userId}`, { method: "DELETE" });
return `User ${userId} deleted`;
},
available: isAdmin ? "enabled" : "disabled",
},
[isAdmin],
);
return <div></div>;
}
Pass webmcp to also register the tool on document.modelContext. Browser
agents that support WebMCP can
then discover and call the tool while the page is open.
function OrderSearch() {
useFrontendTool({
name: "searchOrders",
description: "Search the signed-in user's orders by status",
parameters: z.object({
status: z.enum(["open", "shipped", "delivered"]),
}),
handler: async ({ status }) => {
const orders = await searchOrders(status);
return JSON.stringify(orders);
},
webmcp: {
annotations: { readOnlyHint: true },
},
});
return null;
}
For simple UI-control tools (e.g. toggling a theme), the description field is usually enough for a LangGraph agent to discover and call the tool at the right time. For mandatory tools — ones where the agent must call the tool before answering rather than reasoning from stale context — pair a clear description with an explicit instruction in the agent's system_prompt.
graph = create_agent( # create_agent supersedes the deprecated create_react_agent, which accepts neither middleware= nor system_prompt=
model="openai:gpt-4o",
tools=[],
middleware=[CopilotKitMiddleware()],
state_schema=CopilotKitState,
system_prompt=(
"You are a helpful assistant.\n\n"
"IMPORTANT: When the user asks about current sales data, you MUST call "
"the `fetch_sales_by_month_range` frontend tool first. "
"Never answer from memory or previously seen context values."
),
)
See Ensuring your agent reliably calls frontend tools for the full pattern and a TypeScript example.
name is already registered, the hook logs a warning. Only one tool per name is active at a time.deps is provided, the tool registration is refreshed whenever any dependency value changes, similar to useEffect.webmcp set, the tool is also registered on document.modelContext and unregistered on unmount. The tool needs a description for this (WebMCP rejects tools without one).render function is provided, it is added to the internal render tool calls registry. It receives streaming args (partial during InProgress, complete during Executing and Complete).void.useHumanInTheLoop -- for tools that pause execution and wait for user inputuseRenderToolCall -- for rendering backend tool calls without a client-side handleruseComponent -- convenience wrapper for rendering React components from tool argsuseRenderTool -- register renderer-only tool call UI (named or wildcard)useCopilotAction -- v1 equivalent