Creating custom tools

Learn how to extend Composio's toolkits with your own tools

Custom tools allow you to create your own tools that can be used with Composio.

  1. Standalone tools - Simple tools that don’t require any authentication
  2. Toolkit-based tools - Tools that require authentication and can use toolkit credentials

Creating a Custom Tool

Standalone Tool

A standalone tool is the simplest form of custom tool. It only requires input parameters and an execute function:

1const tool = await composio.tools.createCustomTool({
2 slug: 'CALCULATE_SQUARE',
3 name: 'Calculate Square',
4 description: 'Calculates the square of a number',
5 inputParams: z.object({
6 number: z.number().describe('The number to calculate the square of'),
7 }),
8 execute: async input => {
9 const { number } = input;
10 return {
11 data: { result: number * number },
12 error: null,
13 successful: true,
14 };
15 },
16});

Toolkit-based Tool

A toolkit-based tool has access to two ways of making authenticated requests:

  1. Using executeToolRequest - The recommended way to make authenticated requests to the toolkit’s API endpoints. Composio automatically handles credential injection and baseURL resolution:
1const tool = await composio.tools.createCustomTool({
2 slug: 'GITHUB_STAR_COMPOSIOHQ_REPOSITORY',
3 name: 'Github star composio repositories',
4 toolkitSlug: 'github',
5 description: 'Star any specified repo of `composiohq` user',
6 inputParams: z.object({
7 repository: z.string().describe('The repository to star'),
8 page: z.number().optional().describe('Pagination page number'),
9 customHeader: z.string().optional().describe('Custom header'),
10 }),
11 execute: async (input, connectionConfig, executeToolRequest) => {
12 // This method makes authenticated requests to the relevant API
13 // You can use relative paths!
14 // Composio will automatically inject the baseURL
15 const result = await executeToolRequest({
16 endpoint: `/user/starred/composiohq/${input.repository}`,
17 method: 'PUT',
18 body: {},
19 // Add custom headers or query parameters
20 parameters: [
21 // Add query parameters
22 {
23 name: 'page',
24 value: input.page?.toString() || '1',
25 in: 'query',
26 },
27 // Add custom headers
28 {
29 name: 'x-custom-header',
30 value: input.customHeader || 'default-value',
31 in: 'header',
32 },
33 ],
34 });
35 return result;
36 },
37});
  1. Using connectionConfig - For making direct API calls when needed:
1const tool = await composio.tools.createCustomTool({
2 slug: 'GITHUB_DIRECT_API',
3 name: 'Direct GitHub API Call',
4 description: 'Makes direct calls to GitHub API',
5 toolkitSlug: 'github',
6 inputParams: z.object({
7 repo: z.string().describe('Repository name'),
8 }),
9 execute: async (input, connectionConfig, executeToolRequest) => {
10 // Use connectionConfig for direct API calls
11 const result = await fetch(`https://api.github.com/repos/${input.repo}`, {
12 headers: {
13 Authorization: `Bearer ${connectionConfig.access_token}`,
14 },
15 });
16
17 return {
18 data: await result.json(),
19 error: null,
20 successful: true,
21 };
22
23},
24});

Using Custom Headers and Query Parameters

You can add custom headers and query parameters to your toolkit-based tools using the parameters option in executeToolRequest:

1@composio.tools.custom_tool(toolkit="github")
2def get_issue_info(
3 request: GetIssueInfoInput,
4 execute_request: ExecuteRequestFn,
5 auth_credentials: dict,
6) -> dict:
7 """Get information about a GitHub issue."""
8 response = execute_request(
9 endpoint=f"/repos/composiohq/composio/issues/{request.issue_number}",
10 method="GET",
11 parameters=[
12 {
13 "name": "Accept",
14 "value": "application/vnd.github.v3+json",
15 "type": "header",
16 },
17 {
18 "name": "Authorization",
19 "value": f"Bearer {auth_credentials['access_token']}",
20 "type": "header",
21 },
22 {
23 "name": 'X-Custom-Header',
24 "value": 'custom-value',
25 "type": 'header',
26 },
27 ],
28 )
29 return {"data": response.data}

Executing Custom Tools

You can execute custom tools just like any other tool:

1response = composio.tools.execute(
2 user_id="default",
3 slug="TOOL_SLUG", # For the tool above you can use `get_issue_info.slug`
4 arguments={"issue_number": 1},
5)

Best Practices

  1. Use descriptive names and slugs for your tools
  2. Always provide descriptions for input parameters using describe()
  3. Handle errors gracefully in your execute function
  4. For toolkit-based tools:
    • Prefer executeToolRequest over direct API calls when possible
    • Use relative paths with executeToolRequest - Composio will automatically inject the correct baseURL
    • Use the parameters option to add custom headers or query parameters:
      1parameters: [
      2 { name: 'page', value: '1', in: 'query' }, // Adds ?page=1 to URL
      3 { name: 'x-custom', value: 'value', in: 'header' }, // Adds header
      4];
    • Remember that executeToolRequest can only call tools from the same toolkit
    • Use executeToolRequest to leverage Composio’s automatic credential handling
    • Only use connectionConfig when you need to make direct API calls or interact with different services
  5. Chain multiple toolkit operations using executeToolRequest for better maintainability

Limitations

  1. Custom tools are stored in memory and are not persisted
  2. They need to be recreated when the application restarts
  3. Toolkit-based tools require a valid connected account with the specified toolkit
  4. executeToolRequest can only execute tools from the same toolkit that the custom tool belongs to
  5. Each toolkit-based tool can only use one connected account at a time