How to Create Custom Tools with Custom Logic in the Composio SDK
You can create custom tools in the Composio SDK by defining a tool metadata object with a Zod input schema and an async execution function, then registering it via composio.tools.createCustomTool().
The Composio SDK allows developers to extend the framework's built-in toolset by implementing custom logic that integrates seamlessly with AI agents and workflows. Whether you need to perform domain-specific calculations or interact with proprietary APIs, custom tools provide a standardized way to encapsulate your business logic while maintaining the same ergonomic interface as native Composio tools.
What Are Custom Tools in Composio?
A custom tool is a user-defined Tool object that you register at runtime with the Composio SDK. According to the source code in ts/packages/core/src/models/Tools.ts, each custom tool requires four core components:
- Metadata –
slug,name,description, and optionaltoolkitSlugorconnectedAccountId - Input schema – A Zod object (
inputParams) that validates and describes the tool's arguments - Execution function – An
asynccallback that receives parsed input and returns aToolExecutionResult - Registration – A call to
CustomToolsService.createToolvia the publiccreateCustomToolmethod
Prerequisites and Setup
Before creating custom tools, ensure you have the Composio SDK installed and configured:
npm install @composio/core zod dotenv
Initialize the SDK with your API key:
import { Composio } from '@composio/core';
import 'dotenv/config';
const composio = new Composio({
apiKey: process.env.COMPOSIO_API_KEY,
});
Creating a Custom Tool Step-by-Step
Define Tool Metadata
Start by defining the basic metadata that identifies your tool. The slug must be unique and is used to invoke the tool later:
const customToolSlug = 'CALCULATE_SQUARE_OF_A_NUMBER';
Configure the Input Schema with Zod
Use Zod to define the input parameters. This schema validates arguments at runtime and generates JSON schemas for AI model consumption:
import { z } from 'zod';
const inputParams = z.object({
number: z.number().describe('The number to square'),
});
Implement the Execution Function
The execute function contains your custom logic. It receives the validated input object and optionally a connectionConfig if the tool uses connected accounts. It must return a ToolExecutionResult with data, error, and successful properties:
const execute = async (input: { number: number }) => {
const { number } = input;
const result = Number(number) * Number(number);
return {
data: { result },
error: null,
successful: true
};
};
Registering and Executing Custom Tools
Combine all components and register the tool using createCustomTool, as implemented in src/models/Tools.ts:
const tool = await composio.tools.createCustomTool({
slug: customToolSlug,
name: 'Calculate Square',
description: 'Returns the square of a given number',
inputParams,
execute,
});
console.log('✅ Created tool:', tool);
Execute the tool using the standard execute method, passing the slug and arguments:
const result = await composio.tools.execute(customToolSlug, {
arguments: { number: 3 },
userId: 'default',
});
console.log('🔎 Execution result:', result);
This example follows the pattern shown in ts/examples/custom-tools/src/simple.ts.
Advanced Example: Integrating with External APIs
Custom tools can leverage connected accounts to authenticate with external APIs. This example from the source analysis demonstrates creating a GitHub gist using an authenticated connection:
const gitHubTool = await composio.tools.createCustomTool({
slug: 'CREATE_GIST',
name: 'Create GitHub Gist',
description: 'Creates a public gist on GitHub',
toolkitSlug: 'github', // associate with the GitHub toolkit
connectedAccountId: '123', // optional – ties to a specific user connection
inputParams: z.object({
filename: z.string(),
content: z.string(),
}),
execute: async (input, connectionConfig) => {
// `connectionConfig` contains the OAuth token for the linked GitHub account
const { filename, content } = input;
const response = await fetch('https://api.github.com/gists', {
method: 'POST',
headers: {
Authorization: `token ${connectionConfig.accessToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
public: true,
files: { [filename]: { content } },
}),
});
const data = await response.json();
return { data, error: null, successful: response.ok };
},
});
The connectionConfig parameter provides access tokens and credentials associated with the connectedAccountId, enabling secure API interactions without hardcoding secrets.
Summary
- Custom tools extend the Composio SDK by wrapping your own business logic in the standard
Toolinterface. - Core components include metadata (slug, name, description), a Zod input schema (
inputParams), and an asyncexecutefunction returningToolExecutionResult. - Registration happens via
composio.tools.createCustomTool(), implemented insrc/models/Tools.ts, which delegates toCustomToolsService.createTool. - Execution uses the standard
composio.tools.execute(slug, args)method, treating custom and built-in tools identically. - Connected accounts can be linked via
connectedAccountIdandtoolkitSlugto provide OAuth tokens to the execution function.
Frequently Asked Questions
What is the difference between custom tools and built-in toolkits?
Built-in toolkits are pre-defined collections of tools maintained by Composio (such as GitHub, Slack, or Gmail integrations) that map to external APIs. Custom tools are user-defined implementations that allow you to execute your own logic, calculations, or proprietary API calls while maintaining the same interface as built-in tools. Both are invoked identically via composio.tools.execute().
Can I use custom tools with connected accounts?
Yes. When creating a custom tool, you can specify connectedAccountId and toolkitSlug in the configuration object. When the tool executes, the execute function receives a connectionConfig parameter containing OAuth tokens and credentials associated with that connected account, enabling secure authentication with external services without exposing secrets in your code.
How does input validation work for custom tools?
Input validation is handled automatically by the Zod schema you provide in the inputParams field. When composio.tools.execute() is called, the SDK validates the arguments against this schema before invoking your execute function. If validation fails, the SDK returns an error response without executing your custom logic, ensuring type safety and preventing malformed data from reaching your business logic.
Where are custom tool definitions stored?
Custom tool definitions are registered at runtime through the createCustomTool method in src/models/Tools.ts. While the SDK stores these definitions in memory for immediate use, the Composio backend can optionally persist them depending on your configuration. The tools remain available for the duration of your SDK session and can be invoked repeatedly using their unique slug identifier.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →