Data Mapping with Workflow Variables (Legacy)
This example demonstrates how to use workflow variables to map data between steps in a Mastra workflow.
Use Case: User Registration Process
In this example, we’ll build a simple user registration workflow that:
- Validates user input
- Formats the user data
- Creates a user profile
Implementation
import { LegacyStep, LegacyWorkflow } from "@mastra/core/workflows/legacy";
import { z } from "zod";
// Define our schemas for better type safety
const userInputSchema = z.object({
email: z.string().email(),
name: z.string(),
age: z.number().min(18),
});
const validatedDataSchema = z.object({
isValid: z.boolean(),
validatedData: z.object({
email: z.string(),
name: z.string(),
age: z.number(),
}),
});
const formattedDataSchema = z.object({
userId: z.string(),
formattedData: z.object({
email: z.string(),
displayName: z.string(),
ageGroup: z.string(),
}),
});
const profileSchema = z.object({
profile: z.object({
id: z.string(),
email: z.string(),
displayName: z.string(),
ageGroup: z.string(),
createdAt: z.string(),
}),
});
// Define the workflow
const registrationWorkflow = new LegacyWorkflow({
name: "user-registration",
triggerSchema: userInputSchema,
});
// Step 1: Validate user input
const validateInput = new LegacyStep({
id: "validateInput",
inputSchema: userInputSchema,
outputSchema: validatedDataSchema,
execute: async ({ context }) => {
const { email, name, age } = context;
// Simple validation logic
const isValid = email.includes("@") && name.length > 0 && age >= 18;
return {
isValid,
validatedData: {
email: email.toLowerCase().trim(),
name,
age,
},
};
},
});
// Step 2: Format user data
const formatUserData = new LegacyStep({
id: "formatUserData",
inputSchema: z.object({
validatedData: z.object({
email: z.string(),
name: z.string(),
age: z.number(),
}),
}),
outputSchema: formattedDataSchema,
execute: async ({ context }) => {
const { validatedData } = context;
// Generate a simple user ID
const userId = `user_${Math.floor(Math.random() * 10000)}`;
// Format the data
const ageGroup = validatedData.age < 30 ? "young-adult" : "adult";
return {
userId,
formattedData: {
email: validatedData.email,
displayName: validatedData.name,
ageGroup,
},
};
},
});
// Step 3: Create user profile
const createUserProfile = new LegacyStep({
id: "createUserProfile",
inputSchema: z.object({
userId: z.string(),
formattedData: z.object({
email: z.string(),
displayName: z.string(),
ageGroup: z.string(),
}),
}),
outputSchema: profileSchema,
execute: async ({ context }) => {
const { userId, formattedData } = context;
// In a real app, you would save to a database here
return {
profile: {
id: userId,
...formattedData,
createdAt: new Date().toISOString(),
},
};
},
});
// Build the workflow with variable mappings
registrationWorkflow
// First step gets data from the trigger
.step(validateInput, {
variables: {
email: { step: "trigger", path: "email" },
name: { step: "trigger", path: "name" },
age: { step: "trigger", path: "age" },
},
})
// Format user data with validated data from previous step
.then(formatUserData, {
variables: {
validatedData: { step: validateInput, path: "validatedData" },
},
when: {
ref: { step: validateInput, path: "isValid" },
query: { $eq: true },
},
})
// Create profile with data from the format step
.then(createUserProfile, {
variables: {
userId: { step: formatUserData, path: "userId" },
formattedData: { step: formatUserData, path: "formattedData" },
},
})
.commit();
export default registrationWorkflow;
How to Use This Example
- Create the file as shown above
- Register the workflow in your Mastra instance
- Execute the workflow:
curl --location 'http://localhost:4111/api/workflows/user-registration/start-async' \
--header 'Content-Type: application/json' \
--data '{
"email": "user@example.com",
"name": "John Doe",
"age": 25
}'
Key Takeaways
This example demonstrates several important concepts about workflow variables:
-
Data Mapping: Variables map data from one step to another, creating a clear data flow.
-
Path Access: The
path
property specifies which part of a step’s output to use. -
Conditional Execution: The
when
property allows steps to execute conditionally based on previous step outputs. -
Type Safety: Each step defines input and output schemas for type safety, ensuring that the data passed between steps is properly typed.
-
Explicit Data Dependencies: By defining input schemas and using variable mappings, the data dependencies between steps are made explicit and clear.
For more information on workflow variables, see the Workflow Variables documentation.
Workflows (Legacy)
The following links provide example documentation for legacy workflows:
- Creating a Simple Workflow (Legacy)
- Workflow (Legacy) with Sequential Steps
- Parallel Execution with Steps
- Branching Paths
- Workflow (Legacy) with Conditional Branching (experimental)
- Calling an Agent From a Workflow (Legacy)
- Tool as a Workflow step (Legacy)
- Workflow (Legacy) with Cyclical dependencies
- Human in the Loop Workflow (Legacy)
- Workflow (Legacy) with Suspend and Resume