Skip to main content
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.
async createCustomTool<T extends CustomToolInputParameter>(
  body: CustomToolOptions<T>
): Promise<Tool>
body
CustomToolOptions
required

Examples

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']
});

Execute Function Signature

The execute function receives three parameters:
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:
const input = {
  city: 'New York',
  units: 'celsius'
};

Connection Config

Auth credentials when toolkitSlug is provided:
const connectionConfig = {
  access_token: 'ghp_...',
  token_type: 'Bearer',
  // ... other auth fields
};

Execute Tool Request

Make authenticated API calls to the toolkit:
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' }
  ]
});
data
ToolProxyParams
required

Return Value

The execute function must return a ToolExecuteResponse:
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:
// 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

Tools API

Learn about built-in tools

Toolkits

Browse available toolkits

Connected Accounts

Set up authentication

Providers

Choose your AI framework

Build docs developers (and LLMs) love