CrewAI Tutorial (TypeScript): building conditional routing for beginners

By Cyprian AaronsUpdated 2026-04-21
crewaibuilding-conditional-routing-for-beginnerstypescript

This tutorial shows you how to build a CrewAI workflow in TypeScript that routes tasks conditionally based on the user’s input. You need this when one request can go down different paths, like billing vs claims vs general support, and you want the agent to pick the right specialist without hardcoding every branch.

What You'll Need

  • Node.js 18+ and npm
  • A TypeScript project already set up
  • crewai installed in your project
  • dotenv for environment variables
  • An OpenAI API key in .env
  • Basic familiarity with agents, tasks, and crews in CrewAI

Install the packages:

npm install crewai dotenv
npm install -D typescript tsx @types/node

Create a .env file:

OPENAI_API_KEY=your_api_key_here

Step-by-Step

  1. Start by defining the routing problem clearly. We’ll create one router agent that classifies the request into a route, then use that result to choose which specialist agent should handle the task.
import 'dotenv/config';
import { Agent, Task, Crew, Process } from 'crewai';

type Route = 'billing' | 'claims' | 'general';

type RoutingResult = {
  route: Route;
  reason: string;
};
  1. Build a router task that returns structured output. The important part is forcing the model to classify into one of a few allowed routes so your code can make a deterministic decision afterward.
const routerAgent = new Agent({
  name: 'Router',
  role: 'Request classifier',
  goal: 'Classify incoming requests into billing, claims, or general',
  backstory: 'You are precise and only return valid routing decisions.',
});

const routerTask = new Task({
  description:
    'Classify this customer request into billing, claims, or general: {request}',
  expectedOutput:
    'A JSON object with route and reason fields',
  agent: routerAgent,
});
  1. Add specialist agents for each route. Each agent should have a narrow job so your conditional routing stays simple and easy to maintain.
const billingAgent = new Agent({
  name: 'Billing Specialist',
  role: 'Handles billing questions',
  goal: 'Resolve billing-related issues clearly',
});

const claimsAgent = new Agent({
  name: 'Claims Specialist',
  role: 'Handles insurance claims questions',
  goal: 'Resolve claims-related issues clearly',
});

const generalAgent = new Agent({
  name: 'General Support',
  role: 'Handles general customer questions',
  goal: 'Answer general questions accurately',
});
  1. Create the conditional routing logic in TypeScript. CrewAI does the agent work; your code decides which follow-up task to run based on the router output.
function pickAgent(route: Route) {
  switch (route) {
    case 'billing':
      return billingAgent;
    case 'claims':
      return claimsAgent;
    default:
      return generalAgent;
  }
}

async function run(request: string) {
  const routingCrew = new Crew({
    agents: [routerAgent],
    tasks: [routerTask],
    process: Process.sequential,
  });

  const routingResult = (await routingCrew.kickoff({
    request,
  })) as unknown as RoutingResult;

  const selectedAgent = pickAgent(routingResult.route);

  const answerTask = new Task({
    description: `Answer this request using the selected specialty: ${request}`,
    expectedOutput: 'A concise customer-ready response',
    agent: selectedAgent,
  });

  const answerCrew = new Crew({
    agents: [selectedAgent],
    tasks: [answerTask],
    process: Process.sequential,
  });

  const result = await answerCrew.kickoff();
  console.log({ routingResult, result });
}
  1. Add an entry point and test with a few requests. This makes it easy to confirm that the same code path routes different inputs to different specialists.
const samples = [
  'I was charged twice for my premium this month.',
  'My claim was denied and I need help understanding why.',
];

for (const sample of samples) {
  await run(sample);
}
  1. If you want safer production behavior, validate the route before using it. LLMs occasionally drift outside your allowed values, so guardrails matter even in simple routing flows.
function isRoute(value: string): value is Route {
  return value === 'billing' || value === 'claims' || value === 'general';
}

async function safeRun(request: string) {
  const routingCrew = new Crew({
    agents: [routerAgent],
    tasks: [routerTask],
    process: Process.sequential,
  });

  const routingResult = (await routingCrew.kickoff({ request })) as any;

  if (!isRoute(routingResult.route)) {
    throw new Error(`Invalid route returned: ${routingResult.route}`);
  }

  const selectedAgent = pickAgent(routingResult.route);
}

Testing It

Run the file with tsx or compile it with tsc first. You should see one route chosen for each sample request, followed by an answer generated by the matching specialist agent.

Test three cases manually:

  • A billing question should route to billing
  • A claim status or denial question should route to claims
  • Anything else should fall back to general

If you get unstable results, tighten the router prompt and keep the allowed routes small. In production, log both the raw router output and the final selected path so you can debug misroutes quickly.

Next Steps

  • Add confidence thresholds and fallback handling for low-confidence routes
  • Persist routing decisions in your observability stack for auditability
  • Extend this pattern to multi-step workflows with approval gates and human handoff

Keep learning

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

Related Guides