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/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:

  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.