CrewAI Tutorial (TypeScript): building custom tools for beginners
This tutorial shows you how to build a custom CrewAI tool in TypeScript, wire it into an agent, and call it from a task. You need this when the built-in tools are not enough and your agent has to reach a real internal API, database, or business rule.
What You'll Need
- •Node.js 18+
- •A TypeScript project with
typescriptandtsx - •CrewAI for TypeScript installed in your project
- •An OpenAI API key in
OPENAI_API_KEY - •Basic familiarity with agents, tasks, and crews in CrewAI
- •A simple external action to wrap, like fetching weather, looking up a customer record, or validating an ID
Step-by-Step
- •Start with a clean TypeScript project and install the packages you need. For this example, we’ll use one custom tool that returns mock customer data so you can focus on the tool pattern first.
npm init -y
npm install crewai zod dotenv
npm install -D typescript tsx @types/node
npx tsc --init
- •Create a
.envfile so your model can authenticate. Keep secrets out of source control and load them at runtime.
OPENAI_API_KEY=your_openai_api_key_here
- •Define a custom tool as a normal TypeScript class. The important part is implementing the tool contract with a name, description, input schema, and an async
executemethod.
// src/tools/customerLookupTool.ts
import { Tool } from "crewai";
import { z } from "zod";
export const customerLookupSchema = z.object({
customerId: z.string().min(1),
});
export class CustomerLookupTool extends Tool {
name = "customer_lookup";
description = "Look up a customer by customerId and return basic account details.";
schema = customerLookupSchema;
async execute(input: z.infer<typeof customerLookupSchema>): Promise<string> {
const mockDb: Record<string, { name: string; tier: string; balance: number }> = {
CUST1001: { name: "Amina Khan", tier: "Gold", balance: 1250.5 },
CUST1002: { name: "David Mensah", tier: "Silver", balance: 220.0 },
};
const record = mockDb[input.customerId];
if (!record) return JSON.stringify({ found: false });
return JSON.stringify({ found: true, customerId: input.customerId, ...record });
}
}
- •Create an agent that knows when to use the tool. Keep the instructions specific so the model does not guess or fabricate customer data.
// src/agent.ts
import "dotenv/config";
import { Agent } from "crewai";
import { CustomerLookupTool } from "./tools/customerLookupTool";
export const supportAgent = new Agent({
role: "Customer Support Analyst",
goal: "Answer questions about customer accounts using approved tools only.",
backstory:
"You work in a regulated support environment and must not invent account details.",
tools: [new CustomerLookupTool()],
});
- •Add a task and run the crew. This is where the agent gets prompted to use your custom tool instead of responding from memory.
// src/index.ts
import "dotenv/config";
import { Crew, Task } from "crewai";
import { supportAgent } from "./agent";
async function main() {
const task = new Task({
description:
"Use the customer_lookup tool to check account details for customer ID CUST1001 and summarize the result.",
expectedOutput:
"A short summary containing whether the customer was found, their name, tier, and balance.",
agent: supportAgent,
});
const crew = new Crew({
agents: [supportAgent],
tasks: [task],
});
const result = await crew.kickoff();
console.log(result);
}
main().catch(console.error);
- •Run it with
tsx. If everything is wired correctly, the agent should call your tool and print structured account data in its response.
npx tsx src/index.ts
Testing It
Run the script with CUST1001 first because that record exists in the mock database. You should see output that includes Amina Khan, Gold, and the balance value returned by your tool.
Then change the task description to use CUST9999 and run it again. The tool should return { "found": false }, which proves the agent is actually calling your code instead of making up an answer.
If you want stronger verification, add logging inside execute() before returning the payload. In production systems, I also recommend unit-testing the tool class directly so you can validate schema handling without invoking the full crew.
Next Steps
- •Replace the mock database with a real internal API call using
fetch - •Add Zod validation for richer inputs like date ranges or policy numbers
- •Wrap multiple business actions as separate tools instead of one large tool
Keep learning
- •The complete AI Agents Roadmap — my full 8-step breakdown
- •Free: The AI Agent Starter Kit — PDF checklist + starter code
- •Work with me — I build AI for banks and insurance companies
By Cyprian Aarons, AI Consultant at Topiax.
Want the complete 8-step roadmap?
Grab the free AI Agent Starter Kit — architecture templates, compliance checklists, and a 7-email deep-dive course.
Get the Starter Kit