Skip to main content
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:
name
string
required
Unique identifier for the tool. This is how the agent references the tool.
description
string
Human-readable description of what the tool does. Helps the agent understand when to use it.
parameters
z.ZodType<T>
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
render
React.ComponentType
React component to render the tool’s execution in the UI. Receives props with tool status, arguments, and result.
followUp
boolean
Whether the agent should continue execution after this tool completes. Defaults to true. Set to false to stop agent execution after this tool.
agentId
string
Scope this tool to a specific agent. If specified, only that agent can use this tool.
available
boolean
Whether the tool is currently available. Set to false to temporarily hide the tool without unregistering it. Defaults to true.

Dependencies

deps
ReadonlyArray<unknown>
Optional dependency array (like useEffect). When dependencies change, the tool is re-registered with updated configuration.

Examples

Basic Tool with Handler

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;
}

Tool with Custom Rendering

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;
}

Dynamic Tool Configuration

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"
    />
  );
}

Agent-Scoped Tool

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;
}

Tool with followUp Control

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;
}

Tool Status Lifecycle

When a tool is executed, it goes through three status states:
  1. inProgress - Tool call is streaming in (parameters may be partial)
  2. executing - Parameters are complete, handler is running
  3. 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.

Tool Override Behavior

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;
}

Build docs developers (and LLMs) love