# How to Create Custom Tools with Custom Logic in the Composio SDK

> Learn to build custom tools with unique logic in the Composio SDK. Define metadata, Zod input schemas, and execution functions, then register them easily.

- Repository: [Composio/composio](https://github.com/composiohq/composio)
- Tags: how-to-guide
- Published: 2026-02-19

---

**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`](https://github.com/ComposioHQ/composio/blob/main/ts/packages/core/src/models/Tools.ts), each custom tool requires four core components:

- **Metadata** – `slug`, `name`, `description`, and optional `toolkitSlug` or `connectedAccountId`
- **Input schema** – A Zod object (`inputParams`) that validates and describes the tool's arguments
- **Execution function** – An `async` callback that receives parsed input and returns a `ToolExecutionResult`
- **Registration** – A call to `CustomToolsService.createTool` via the public `createCustomTool` method

## Prerequisites and Setup

Before creating custom tools, ensure you have the Composio SDK installed and configured:

```bash
npm install @composio/core zod dotenv

```

Initialize the SDK with your API key:

```typescript
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:

```typescript
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:

```typescript
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:

```typescript
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`](https://github.com/ComposioHQ/composio/blob/main/src/models/Tools.ts):

```typescript
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:

```typescript
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`](https://github.com/ComposioHQ/composio/blob/main/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:

```typescript
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 `Tool` interface.
- **Core components** include metadata (slug, name, description), a Zod input schema (`inputParams`), and an async `execute` function returning `ToolExecutionResult`.
- **Registration** happens via `composio.tools.createCustomTool()`, implemented in [`src/models/Tools.ts`](https://github.com/ComposioHQ/composio/blob/main/src/models/Tools.ts), which delegates to `CustomToolsService.createTool`.
- **Execution** uses the standard `composio.tools.execute(slug, args)` method, treating custom and built-in tools identically.
- **Connected accounts** can be linked via `connectedAccountId` and `toolkitSlug` to 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`](https://github.com/ComposioHQ/composio/blob/main/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.