Overview
The useFrontendTool hook dynamically registers a frontend tool that AI agents can invoke. Frontend tools run in the browser and can access client-side state, interact with the UI, or perform client-side operations.
Usage
import { useFrontendTool } from "@copilotkit/react" ;
import { z } from "zod" ;
function MyComponent () {
useFrontendTool ({
name: "searchProducts" ,
description: "Search the product catalog" ,
parameters: z . object ({
query: z . string (). describe ( "Search query" ),
}),
handler : async ({ query }) => {
const results = await searchAPI ( query );
return results ;
},
});
return < div > ... </ div > ;
}
Type Signature
function useFrontendTool < T extends Record < string , unknown >>(
tool : ReactFrontendTool < T >,
deps ?: ReadonlyArray < unknown >
) : void
Parameters
tool
ReactFrontendTool<T>
required
The tool definition object with the following properties: Unique identifier for the tool. Used by agents to invoke the tool. Human-readable description of what the tool does. Helps the AI understand when to use it. description : "Get current weather information for a city"
Zod schema defining the tool’s input parameters. The schema provides type safety and validation. parameters : z . object ({
city: z . string (). describe ( "City name" ),
units: z . enum ([ "celsius" , "fahrenheit" ]). optional ()
})
handler
(args: T, context: FrontendToolHandlerContext) => Promise<unknown>
Async function that executes when the tool is called. Receives validated parameters and context. Handler Context:
toolCall: The AG-UI tool call object
agent: The agent instance that invoked the tool
handler : async ({ city , units }, context ) => {
const weather = await fetchWeather ( city , units );
return weather ;
}
render
(props: RenderProps) => React.ReactElement
Optional React component to render custom UI for this tool call in the chat. Render Props:
name: Tool name
args: Tool parameters (partial during inProgress, complete during executing/complete)
status: One of "inProgress", "executing", or "complete"
result: Tool result (only available when status === "complete")
render : ({ args , status , result }) => {
if ( status === "executing" ) {
return < div > Fetching weather for { args . city } ... </ div > ;
}
return < div > { result } </ div > ;
}
Whether the agent should follow up after this tool executes. Set to false to prevent automatic follow-up messages.
Constrain this tool to a specific agent. If specified, only that agent can use this tool. agentId : "research-agent"
Whether this tool is currently available to agents. Set to false to temporarily disable without unregistering. available : userHasPermission
Optional dependency array (like useEffect). When dependencies change, the tool is re-registered with updated configuration. useFrontendTool ( tool , [ userId , permissions ]);
The tool is automatically re-registered when tool.available changes, even without including it in deps.
Return Value
This hook does not return a value. The tool is registered with CopilotKit and automatically unregistered on component unmount.
Examples
import { useFrontendTool } from "@copilotkit/react" ;
import { z } from "zod" ;
function WeatherWidget () {
useFrontendTool ({
name: "getWeather" ,
description: "Get weather for a city" ,
parameters: z . object ({
city: z . string (). describe ( "City name" ),
}),
handler : async ({ city }) => {
const response = await fetch ( `/api/weather?city= ${ city } ` );
const data = await response . json ();
return `Weather in ${ city } : ${ data . temperature } °C, ${ data . conditions } ` ;
},
});
return < div > Weather widget ready </ div > ;
}
import { useFrontendTool } from "@copilotkit/react" ;
import { z } from "zod" ;
function ProductSearch () {
useFrontendTool ({
name: "searchProducts" ,
description: "Search for products in the catalog" ,
parameters: z . object ({
query: z . string (),
category: z . string (). optional (),
}),
handler : async ({ query , category }) => {
const results = await searchProducts ( query , category );
return JSON . stringify ( results );
},
render : ({ args , status , result }) => {
if ( status === "inProgress" ) {
return < div > Preparing search... </ div > ;
}
if ( status === "executing" ) {
return (
< div className = "search-loading" >
Searching for " { args . query } "
{ args . category && ` in ${ args . category } ` } ...
</ div >
);
}
const products = JSON . parse ( result );
return (
< div className = "product-results" >
< h3 > Found { products . length } products </ h3 >
< ul >
{ products . map ( p => (
< li key = { p . id } > { p . name } - $ { p . price } </ li >
)) }
</ ul >
</ div >
);
},
});
return < div > Product search enabled </ div > ;
}
import { useFrontendTool } from "@copilotkit/react" ;
import { z } from "zod" ;
function AdminTools ({ user }) {
const isAdmin = user . role === "admin" ;
useFrontendTool ({
name: "deleteUser" ,
description: "Delete a user account (admin only)" ,
parameters: z . object ({
userId: z . string (),
}),
handler : async ({ userId }) => {
await deleteUserAccount ( userId );
return `User ${ userId } deleted` ;
},
available: isAdmin , // Only available to admins
}, [ isAdmin ]); // Re-register when admin status changes
return < div > Admin tools { isAdmin ? "enabled" : "disabled" } </ div > ;
}
import { useFrontendTool } from "@copilotkit/react" ;
import { z } from "zod" ;
function ResearchTools () {
// This tool is only available to the research agent
useFrontendTool ({
name: "searchPapers" ,
description: "Search academic papers" ,
parameters: z . object ({
query: z . string (),
year: z . number (). optional (),
}),
handler : async ({ query , year }) => {
const papers = await searchAcademicPapers ( query , year );
return papers ;
},
agentId: "research-agent" ,
});
return < div > Research tools ready </ div > ;
}
Tool with Context
import { useFrontendTool } from "@copilotkit/react" ;
import { z } from "zod" ;
function DatabaseTools () {
useFrontendTool ({
name: "queryDatabase" ,
description: "Query the database" ,
parameters: z . object ({
query: z . string (),
}),
handler : async ({ query }, context ) => {
// Access the agent and tool call via context
console . log ( "Called by agent:" , context . agent . agentId );
console . log ( "Tool call ID:" , context . toolCall . id );
const results = await runQuery ( query );
return results ;
},
});
return < div > Database tools ready </ div > ;
}
import { useFrontendTool } from "@copilotkit/react" ;
import { z } from "zod" ;
import { useState } from "react" ;
function ConfigurableSearch () {
const [ searchScope , setSearchScope ] = useState ( "all" );
useFrontendTool ({
name: "search" ,
description: `Search content (current scope: ${ searchScope } )` ,
parameters: z . object ({
query: z . string (),
}),
handler : async ({ query }) => {
// Handler uses current searchScope value
return await searchWithScope ( query , searchScope );
},
}, [ searchScope ]); // Re-register when scope changes
return (
< div >
< select value = { searchScope } onChange = { e => setSearchScope ( e . target . value ) } >
< option value = "all" > All </ option >
< option value = "docs" > Documentation </ option >
< option value = "code" > Code </ option >
</ select >
</ div >
);
}
import { useFrontendTool } from "@copilotkit/react" ;
import { z } from "zod" ;
function QuickActions () {
useFrontendTool ({
name: "copyToClipboard" ,
description: "Copy text to clipboard" ,
parameters: z . object ({
text: z . string (),
}),
handler : async ({ text }) => {
await navigator . clipboard . writeText ( text );
return "Copied!" ;
},
followUp: false , // Don't generate follow-up message
});
return < div > Quick actions ready </ div > ;
}
Behavior
Registration
Tools are registered when the component mounts
If a tool with the same name already exists for the same agent, it is overridden with a warning
Tools are automatically unregistered when the component unmounts
Rendering
If a render function is provided, it persists in the render registry even after unmount
This allows tool calls in chat history to continue rendering their custom UI
Each tool is identified by the combination of name and agentId
Re-registration
Tools are re-registered when:
The deps array changes
The tool.available property changes
The tool.name changes
Notes
If you’re passing an inline object to useFrontendTool, wrap it in useMemo to avoid unnecessary re-registrations: const tool = useMemo (() => ({
name: "myTool" ,
// ... rest of tool config
}), [ /* dependencies */ ]);
useFrontendTool ( tool );
For tools registered at the provider level (via CopilotKitProvider props), use a stable array reference. For dynamic tools that change frequently, use useFrontendTool instead.