CrewAI Tutorial (TypeScript): parsing structured output for beginners
This tutorial shows you how to make a CrewAI TypeScript agent return structured data you can safely parse into a typed object. You need this when plain text output is too brittle for production code and you want predictable fields like name, status, or priority instead of scraping prose.
What You'll Need
- •Node.js 18+
- •A TypeScript project initialized with
npm init -y - •CrewAI TypeScript package installed
- •An OpenAI API key
- •A
.envfile for secrets - •Basic familiarity with agents, tasks, and crews in CrewAI
Install the dependencies:
npm install @crewai/core openai dotenv zod
npm install -D typescript tsx @types/node
Set up your environment variables:
OPENAI_API_KEY=your_api_key_here
Step-by-Step
- •First, define the shape of the output you expect. Use Zod so your parser can validate the model response before your app trusts it.
import { z } from "zod";
export const TicketSchema = z.object({
ticketId: z.string(),
priority: z.enum(["low", "medium", "high"]),
summary: z.string(),
owner: z.string(),
});
export type Ticket = z.infer<typeof TicketSchema>;
- •Next, create an agent and a task that instructs the model to return JSON only. The important part is being explicit about keys and avoiding extra text around the payload.
import "dotenv/config";
import { Agent, Task, Crew } from "@crewai/core";
const agent = new Agent({
name: "Support Parser",
role: "Support analyst",
goal: "Extract structured ticket data from incident notes",
backstory: "You convert messy incident notes into clean JSON.",
});
const task = new Task({
description:
"Read the incident note and return JSON with ticketId, priority, summary, and owner.",
expectedOutput:
'{"ticketId":"string","priority":"low|medium|high","summary":"string","owner":"string"}',
agent,
});
- •Now execute the crew and capture the raw response. In many cases, CrewAI returns text that looks like JSON but still needs parsing and validation before you use it downstream.
async function main() {
const crew = new Crew({
agents: [agent],
tasks: [task],
});
const result = await crew.kickoff({
inputs: {
note: "INC-1042 says payment retries failed for customer billing. Assign to Maya. Priority is high.",
},
});
console.log("Raw result:");
console.log(result);
}
main();
- •Add a parser that extracts JSON from the response and validates it against your schema. This is the part that makes the workflow production-safe because malformed output fails fast.
import { TicketSchema, type Ticket } from "./schema";
function parseTicket(raw: string): Ticket {
const match = raw.match(/\{[\s\S]*\}/);
if (!match) {
throw new Error("No JSON object found in model output");
}
const parsed = JSON.parse(match[0]);
return TicketSchema.parse(parsed);
}
- •Put it together in one executable script so you can run end-to-end testing locally. This version prints both the raw model output and the validated object.
import "dotenv/config";
import { Agent, Task, Crew } from "@crewai/core";
import { z } from "zod";
const TicketSchema = z.object({
ticketId: z.string(),
priority: z.enum(["low", "medium", "high"]),
summary: z.string(),
owner: z.string(),
});
function parseTicket(raw: string) {
const match = raw.match(/\{[\s\S]*\}/);
if (!match) throw new Error("No JSON object found in model output");
return TicketSchema.parse(JSON.parse(match[0]));
}
async function main() {
const agent = new Agent({
name: "Support Parser",
role: "Support analyst",
goal: "Extract structured ticket data from incident notes",
backstory: "You convert messy incident notes into clean JSON.",
});
const task = new Task({
description:
"Read the incident note and return JSON with ticketId, priority, summary, and owner.",
expectedOutput:
'{"ticketId":"string","priority":"low|medium|high","summary":"string","owner":"string"}',
agent,
});
const crew = new Crew({ agents: [agent], tasks: [task] });
const result = await crew.kickoff({
inputs: {
note: "INC-1042 says payment retries failed for customer billing. Assign to Maya. Priority is high.",
},
});
const raw = String(result);
console.log(raw);
const ticket = parseTicket(raw);
console.log(ticket);
}
main().catch(console.error);
Testing It
Run the script with npx tsx src/index.ts or whatever path you used for the file. If everything is wired correctly, you should see a raw completion first and then a validated JavaScript object printed after parsing.
Test failure cases too. Change the prompt so the model returns extra commentary or removes one field; your parser should throw before bad data reaches your app.
If you want stronger guarantees, log validation errors from Zod separately from JSON parse errors. That makes it obvious whether the issue was malformed syntax or a schema mismatch.
Next Steps
- •Add retry logic when parsing fails so the agent gets one more chance to produce valid JSON.
- •Move from regex extraction to a stricter response format contract if your provider supports it.
- •Extend this pattern to arrays of objects, then validate each item with Zod before storing results
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