> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/composiohq/composio/llms.txt
> Use this file to discover all available pages before exploring further.

# Custom Tools API

> Create and manage custom tools with your own logic

Custom tools allow you to extend Composio with your own implementations while keeping a consistent interface with built-in tools. Custom tools can be standalone or integrate with existing toolkits for authentication.

## Creating Custom Tools

### createCustomTool()

Create a custom tool with your own execution logic.

```typescript theme={null}
async createCustomTool<T extends CustomToolInputParameter>(
  body: CustomToolOptions<T>
): Promise<Tool>
```

<ParamField path="body" type="CustomToolOptions" required>
  <Expandable title="properties">
    <ParamField path="name" type="string" required>
      Human-readable tool name
    </ParamField>

    <ParamField path="slug" type="string" required>
      Unique tool identifier (UPPERCASE\_SNAKE\_CASE)
    </ParamField>

    <ParamField path="description" type="string" required>
      What the tool does (shown to AI models)
    </ParamField>

    <ParamField path="inputParams" type="ZodSchema" required>
      Zod schema for input validation
    </ParamField>

    <ParamField path="execute" type="function" required>
      Async function that executes the tool
    </ParamField>

    <ParamField path="toolkitSlug" type="string" optional>
      Associate with a toolkit for authentication
    </ParamField>

    <ParamField path="userId" type="string" optional>
      User ID (required if toolkitSlug is provided)
    </ParamField>

    <ParamField path="connectedAccountId" type="string" optional>
      Specific connected account to use
    </ParamField>
  </Expandable>
</ParamField>

## Examples

<Tabs>
  <Tab title="Simple Custom Tool">
    ```typescript theme={null}
    import { z } from 'zod';

    const weatherTool = await composio.tools.createCustomTool({
      name: 'Get Weather',
      slug: 'GET_WEATHER',
      description: 'Get current weather for a city',
      inputParams: z.object({
        city: z.string().describe('City name'),
        units: z.enum(['celsius', 'fahrenheit']).optional().describe('Temperature units')
      }),
      execute: async (input) => {
        // Your custom logic
        const response = await fetch(
          `https://api.weather.com/v1/current?city=${input.city}&units=${input.units || 'celsius'}`
        );
        const data = await response.json();

        return {
          data: {
            temperature: data.temp,
            condition: data.condition,
            humidity: data.humidity
          },
          error: null,
          successful: true
        };
      }
    });

    // Use with any provider
    const tools = await composio.tools.get('default', {
      tools: ['GET_WEATHER']
    });
    ```
  </Tab>

  <Tab title="With Toolkit Integration">
    ```typescript theme={null}
    import { z } from 'zod';

    // Create a custom tool that uses GitHub authentication
    const advancedSearchTool = await composio.tools.createCustomTool({
      name: 'GitHub Advanced Search',
      slug: 'GITHUB_ADVANCED_SEARCH',
      description: 'Search GitHub repositories with advanced filters',
      toolkitSlug: 'github', // Use GitHub auth
      userId: 'user_123',
      inputParams: z.object({
        query: z.string().describe('Search query'),
        language: z.string().optional().describe('Programming language'),
        minStars: z.number().optional().describe('Minimum stars')
      }),
      execute: async (input, connectionConfig, executeToolRequest) => {
        // executeToolRequest makes authenticated API calls
        const searchQuery = [
          input.query,
          input.language ? `language:${input.language}` : '',
          input.minStars ? `stars:>=${input.minStars}` : ''
        ].filter(Boolean).join(' ');

        const result = await executeToolRequest({
          endpoint: '/search/repositories',
          method: 'GET',
          parameters: [
            { name: 'q', in: 'query', value: searchQuery },
            { name: 'sort', in: 'query', value: 'stars' },
            { name: 'order', in: 'query', value: 'desc' }
          ]
        });

        return result;
      }
    });
    ```
  </Tab>

  <Tab title="Database Query Tool">
    ```typescript theme={null}
    import { z } from 'zod';
    import { Pool } from 'pg';

    const pool = new Pool({
      connectionString: process.env.DATABASE_URL
    });

    const dbQueryTool = await composio.tools.createCustomTool({
      name: 'Database Query',
      slug: 'DB_QUERY',
      description: 'Execute a read-only database query',
      inputParams: z.object({
        table: z.string().describe('Table name'),
        columns: z.array(z.string()).describe('Columns to select'),
        where: z.record(z.unknown()).optional().describe('WHERE conditions')
      }),
      execute: async (input) => {
        try {
          const columns = input.columns.join(', ');
          let query = `SELECT ${columns} FROM ${input.table}`;
          const values: unknown[] = [];

          if (input.where) {
            const conditions = Object.entries(input.where)
              .map(([key, _], i) => `${key} = $${i + 1}`)
              .join(' AND ');
            query += ` WHERE ${conditions}`;
            values.push(...Object.values(input.where));
          }

          const result = await pool.query(query, values);

          return {
            data: {
              rows: result.rows,
              count: result.rowCount
            },
            error: null,
            successful: true
          };
        } catch (error) {
          return {
            data: {},
            error: error instanceof Error ? error.message : 'Unknown error',
            successful: false
          };
        }
      }
    });
    ```
  </Tab>

  <Tab title="API Wrapper Tool">
    ```typescript theme={null}
    import { z } from 'zod';

    const cryptoPriceTool = await composio.tools.createCustomTool({
      name: 'Get Crypto Price',
      slug: 'GET_CRYPTO_PRICE',
      description: 'Get current cryptocurrency price',
      inputParams: z.object({
        symbol: z.string().describe('Crypto symbol (e.g., BTC, ETH)'),
        currency: z.string().default('USD').describe('Target currency')
      }),
      execute: async (input) => {
        const response = await fetch(
          `https://api.coingecko.com/api/v3/simple/price?ids=${input.symbol}&vs_currencies=${input.currency}`
        );

        if (!response.ok) {
          return {
            data: {},
            error: 'Failed to fetch crypto price',
            successful: false
          };
        }

        const data = await response.json();

        return {
          data: {
            symbol: input.symbol,
            price: data[input.symbol.toLowerCase()]?.[input.currency.toLowerCase()],
            currency: input.currency,
            timestamp: new Date().toISOString()
          },
          error: null,
          successful: true
        };
      }
    });
    ```
  </Tab>
</Tabs>

## Execute Function Signature

The `execute` function receives three parameters:

```typescript theme={null}
execute: async (
  input: T, // Parsed and validated input
  connectionConfig: ConnectionData | null, // Auth credentials (if toolkitSlug provided)
  executeToolRequest: (data: ToolProxyParams) => Promise<ToolExecuteResponse> // Make authenticated API calls
) => Promise<ToolExecuteResponse>
```

### Input Parameter

Parsed and validated input matching your Zod schema:

```typescript theme={null}
const input = {
  city: 'New York',
  units: 'celsius'
};
```

### Connection Config

Auth credentials when `toolkitSlug` is provided:

```typescript theme={null}
const connectionConfig = {
  access_token: 'ghp_...',
  token_type: 'Bearer',
  // ... other auth fields
};
```

### Execute Tool Request

Make authenticated API calls to the toolkit:

```typescript theme={null}
const result = await executeToolRequest({
  endpoint: '/repos/owner/repo/issues',
  method: 'POST',
  body: {
    title: 'Issue title',
    body: 'Issue description'
  },
  parameters: [
    { name: 'state', in: 'query', value: 'open' }
  ]
});
```

<ParamField path="data" type="ToolProxyParams" required>
  <Expandable title="properties">
    <ParamField path="endpoint" type="string" required>
      API endpoint path
    </ParamField>

    <ParamField path="method" type="string" required>
      HTTP method: GET, POST, PUT, DELETE, etc.
    </ParamField>

    <ParamField path="body" type="Record<string, unknown>" optional>
      Request body
    </ParamField>

    <ParamField path="parameters" type="Parameter[]" optional>
      Query or header parameters
    </ParamField>
  </Expandable>
</ParamField>

## Return Value

The `execute` function must return a `ToolExecuteResponse`:

```typescript theme={null}
interface ToolExecuteResponse {
  data: Record<string, unknown>; // Tool output
  error: string | null; // Error message if failed
  successful: boolean; // Whether execution succeeded
  logId?: string; // Optional log ID
  sessionInfo?: Record<string, unknown>; // Optional session data
}
```

## Using Custom Tools

Once created, custom tools work like any built-in tool:

```typescript theme={null}
// Get the custom tool wrapped for your provider
const tools = await composio.tools.get('default', {
  tools: ['GET_WEATHER', 'GITHUB_ADVANCED_SEARCH']
});

// Execute directly
const result = await composio.tools.execute('GET_WEATHER', {
  userId: 'default',
  arguments: {
    city: 'San Francisco',
    units: 'fahrenheit'
  }
});

console.log(result.data);
```

## Best Practices

1. **Clear Descriptions**: Write clear tool descriptions for AI models
2. **Input Validation**: Use Zod for robust input validation
3. **Error Handling**: Return proper error messages in the response
4. **Type Safety**: Use TypeScript for type-safe implementations
5. **Idempotency**: Make tools idempotent when possible
6. **Rate Limiting**: Handle rate limits in your implementation
7. **Logging**: Log errors for debugging

## Limitations

* Custom tools are stored in-memory (not persisted)
* Re-create custom tools on each SDK initialization
* Cannot use `executeToolRequest` without `toolkitSlug`
* Custom tools require manual version management

## Next Steps

<CardGroup cols={2}>
  <Card title="Tools API" icon="wrench" href="/typescript/api/tools">
    Learn about built-in tools
  </Card>

  <Card title="Toolkits" icon="box" href="/typescript/api/toolkits">
    Browse available toolkits
  </Card>

  <Card title="Connected Accounts" icon="link" href="/typescript/api/connected-accounts">
    Set up authentication
  </Card>

  <Card title="Providers" icon="plug" href="/typescript/providers/overview">
    Choose your AI framework
  </Card>
</CardGroup>
