Data Mapping with Workflow Variables
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 { Step, Workflow } from "@mastra/core/workflows";
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 Workflow({
name: "user-registration",
triggerSchema: userInputSchema,
});
// Step 1: Validate user input
const validateInput = new Step({
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 Step({
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 Step({
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/execute' \
--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.