The useFrontendTool hook allows you to register tools that AI agents can discover and execute. These tools run in the browser and can include custom UI rendering, making them perfect for interactive experiences.
Basic Usage
import { useFrontendTool } from "@copilotkit/react-core" ;
import { z } from "zod" ;
function WeatherTool () {
useFrontendTool ({
name: "getWeather" ,
description: "Get the current weather for a location" ,
parameters: z . object ({
location: z . string (). describe ( "City name" )
}),
handler : async ({ location }) => {
const response = await fetch ( `/api/weather?location= ${ location } ` );
return await response . json ();
}
});
return null ;
}
Parameters
The hook accepts a single tool object with the following properties:
Unique identifier for the tool. This is how the agent references the tool.
Human-readable description of what the tool does. Helps the agent understand when to use it.
Zod schema defining the tool’s parameters. Used for validation and type inference.
handler
(args: T, context) => Promise<unknown>
Async function that executes when the agent calls the tool. Receives the arguments and execution context. Context includes:
toolCall - The tool call instance
agent - The agent that invoked the tool
React component to render the tool’s execution in the UI. Receives props with tool status, arguments, and result.
Whether the agent should continue execution after this tool completes. Defaults to true. Set to false to stop agent execution after this tool.
Scope this tool to a specific agent. If specified, only that agent can use this tool.
Whether the tool is currently available. Set to false to temporarily hide the tool without unregistering it. Defaults to true.
Dependencies
Optional dependency array (like useEffect). When dependencies change, the tool is re-registered with updated configuration.
Examples
import { useFrontendTool } from "@copilotkit/react-core" ;
import { z } from "zod" ;
function CalculatorTool () {
useFrontendTool ({
name: "calculate" ,
description: "Perform basic arithmetic calculations" ,
parameters: z . object ({
operation: z . enum ([ "add" , "subtract" , "multiply" , "divide" ]),
a: z . number (),
b: z . number ()
}),
handler : async ({ operation , a , b }) => {
switch ( operation ) {
case "add" : return a + b ;
case "subtract" : return a - b ;
case "multiply" : return a * b ;
case "divide" : return b !== 0 ? a / b : "Error: Division by zero" ;
}
}
});
return null ;
}
import { useFrontendTool } from "@copilotkit/react-core" ;
import { z } from "zod" ;
function SearchTool () {
useFrontendTool ({
name: "searchDocs" ,
description: "Search documentation" ,
parameters: z . object ({
query: z . string ()
}),
handler : async ({ query }) => {
const results = await fetch ( `/api/search?q= ${ query } ` );
return await results . json ();
},
render : ({ status , args , result }) => {
if ( status === "inProgress" ) {
return < div > Searching for: { args . query } ... </ div > ;
}
if ( status === "executing" ) {
return < div > Processing search for: { args . query } </ div > ;
}
if ( status === "complete" ) {
const data = JSON . parse ( result );
return (
< div >
< h4 > Search Results for " { args . query } " </ h4 >
< ul >
{ data . results . map (( item : any ) => (
< li key = { item . id } > { item . title } </ li >
)) }
</ ul >
</ div >
);
}
}
});
return null ;
}
import { useFrontendTool } from "@copilotkit/react-core" ;
import { z } from "zod" ;
import { useState } from "react" ;
function DynamicTool () {
const [ apiKey , setApiKey ] = useState ( "" );
useFrontendTool (
{
name: "callExternalAPI" ,
description: "Call external API with authentication" ,
parameters: z . object ({
endpoint: z . string ()
}),
available: apiKey !== "" , // Only available when API key is set
handler : async ({ endpoint }) => {
const response = await fetch ( endpoint , {
headers: { Authorization: `Bearer ${ apiKey } ` }
});
return await response . json ();
}
},
[ apiKey ] // Re-register when apiKey changes
);
return (
< input
value = { apiKey }
onChange = { ( e ) => setApiKey ( e . target . value ) }
placeholder = "Enter API key"
/>
);
}
import { useFrontendTool } from "@copilotkit/react-core" ;
import { z } from "zod" ;
function ResearchTools () {
// This tool is only available to the research agent
useFrontendTool ({
name: "analyzePaper" ,
description: "Analyze a research paper" ,
agentId: "research-agent" ,
parameters: z . object ({
paperUrl: z . string ()
}),
handler : async ({ paperUrl }) => {
// Research-specific logic
return { summary: "..." };
}
});
// This tool is globally available
useFrontendTool ({
name: "generalSearch" ,
description: "General purpose search" ,
parameters: z . object ({
query: z . string ()
}),
handler : async ({ query }) => {
// Available to all agents
return { results: [] };
}
});
return null ;
}
import { useFrontendTool } from "@copilotkit/react-core" ;
import { z } from "zod" ;
function FinalAnswerTool () {
useFrontendTool ({
name: "provideFinalAnswer" ,
description: "Provide the final answer and stop processing" ,
parameters: z . object ({
answer: z . string ()
}),
followUp: false , // Stop agent execution after this tool
handler : async ({ answer }) => {
return { finalAnswer: answer };
},
render : ({ args }) => (
< div className = "final-answer" >
< h3 > Final Answer </ h3 >
< p > { args . answer } </ p >
</ div >
)
});
return null ;
}
When a tool is executed, it goes through three status states:
inProgress - Tool call is streaming in (parameters may be partial)
executing - Parameters are complete, handler is running
complete - Handler finished, result is available
Your render component receives different props based on the status:
type RenderProps =
| { status : "inProgress" ; args : Partial < T >; result : undefined }
| { status : "executing" ; args : T ; result : undefined }
| { status : "complete" ; args : T ; result : string };
Best Practices
Keep Tools Focused Each tool should do one thing well. Create multiple small tools rather than one large multi-purpose tool.
Provide Clear Descriptions Good descriptions help the agent understand when to use each tool. Include examples in the description if helpful.
Handle Errors Gracefully Always handle errors in your handler function. Return error objects rather than throwing exceptions.
Optimize Re-registration Only include necessary dependencies in the deps array. Unnecessary re-registrations can impact performance.
When multiple tools are registered with the same name and agentId, the latest registration wins. The console will show a warning when this happens:
Tool 'toolName' already exists for agent 'agentId'.
Overriding with latest registration.
Cleanup
When a component unmounts, the tool’s handler is removed but the renderer persists. This ensures historical tool calls in the chat remain visible.
TypeScript
interface ReactFrontendTool < T extends Record < string , unknown > = Record < string , unknown >> {
name : string ;
description ?: string ;
parameters ?: z . ZodType < T >;
handler ?: ( args : T , context : FrontendToolHandlerContext ) => Promise < unknown >;
render ?: React . ComponentType < RenderToolProps < T >>;
followUp ?: boolean ;
agentId ?: string ;
available ?: boolean ;
}
interface FrontendToolHandlerContext {
toolCall : ToolCall ;
agent : AbstractAgent ;
}