Skip to Content
ExamplesWorkflowsWorkflow Variables

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:

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

Implementation

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

  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.