Skip to main content

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:

  1. Validates user input
  2. Formats the user data
  3. Creates a user profile

Implementation

src/mastra/workflows/user-registration.ts
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

  1. Create the file as shown above
  2. Register the workflow in your Mastra instance
  3. 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:

  1. Data Mapping: Variables map data from one step to another, creating a clear data flow.

  2. Path Access: The path property specifies which part of a step's output to use.

  3. Conditional Execution: The when property allows steps to execute conditionally based on previous step outputs.

  4. Type Safety: Each step defines input and output schemas for type safety, ensuring that the data passed between steps is properly typed.

  5. 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: