DocsWorkflowsVariables

Data Mapping with Workflow Variables

Workflow variables in Mastra provide a powerful mechanism for mapping data between steps, allowing you to create dynamic data flows and pass information from one step to another.

Understanding Workflow Variables

In Mastra workflows, variables serve as a way to:

  • Map data from trigger inputs to step inputs
  • Pass outputs from one step to inputs of another step
  • Access nested properties within step outputs
  • Create more flexible and reusable workflow steps

Using Variables for Data Mapping

Basic Variable Mapping

You can map data between steps using the variables property when adding a step to your workflow:

src/mastra/workflows/index.ts
const workflow = new Workflow({
  name: 'data-mapping-workflow',
  triggerSchema: z.object({
    inputData: z.string(),
  }),
});
 
workflow
  .step(step1, {
    variables: {
      // Map trigger data to step input
      inputData: { step: 'trigger', path: 'inputData' }
    }
  })
  .then(step2, {
    variables: {
      // Map output from step1 to input for step2
      previousValue: { step: step1, path: 'outputField' }
    }
  })
  .commit();

Accessing Nested Properties

You can access nested properties using dot notation in the path field:

src/mastra/workflows/index.ts
workflow
  .step(step1)
  .then(step2, {
    variables: {
      // Access a nested property from step1's output
      nestedValue: { step: step1, path: 'nested.deeply.value' }
    }
  })
  .commit();

Mapping Entire Objects

You can map an entire object by using . as the path:

src/mastra/workflows/index.ts
workflow
  .step(step1, {
    variables: {
      // Map the entire trigger data object
      triggerData: { step: 'trigger', path: '.' }
    }
  })
  .commit();

Variable Resolution

When a workflow executes, Mastra resolves variables at runtime by:

  1. Identifying the source step specified in the step property
  2. Retrieving the output from that step
  3. Navigating to the specified property using the path
  4. Injecting the resolved value into the target step’s context

Examples

Mapping from Trigger Data

This example shows how to map data from the workflow trigger to a step:

src/mastra/workflows/trigger-mapping.ts
import { Step, Workflow } from "@mastra/core/workflows";
import { z } from "zod";
 
// Define a step that needs user input
const processUserInput = new Step({
  id: "processUserInput",
  execute: async ({ context }) => {
    // The inputData will be available in context because of the variable mapping
    const { inputData } = context;
 
    return {
      processedData: `Processed: ${inputData}`
    };
  },
});
 
// Create the workflow
const workflow = new Workflow({
  name: "trigger-mapping",
  triggerSchema: z.object({
    inputData: z.string(),
  }),
});
 
// Map the trigger data to the step
workflow
  .step(processUserInput, {
    variables: {
      inputData: { step: 'trigger', path: 'inputData' },
    }
  })
  .commit();

Mapping Between Steps

This example demonstrates mapping data from one step to another:

src/mastra/workflows/step-mapping.ts
import { Step, Workflow } from "@mastra/core/workflows";
import { z } from "zod";
 
// Step 1: Generate data
const generateData = new Step({
  id: "generateData",
  outputSchema: z.object({
    nested: z.object({
      value: z.string(),
    }),
  }),
  execute: async () => {
    return {
      nested: {
        value: "step1-data"
      }
    };
  },
});
 
// Step 2: Process the data from step 1
const processData = new Step({
  id: "processData",
  inputSchema: z.object({
    previousValue: z.string(),
  }),
  execute: async ({ context }) => {
    // previousValue will be available because of the variable mapping
    const { previousValue } = context;
 
    return {
      result: `Processed: ${previousValue}`
    };
  },
});
 
// Create the workflow
const workflow = new Workflow({
  name: "step-mapping",
});
 
// Map data from step1 to step2
workflow
  .step(generateData)
  .then(processData, {
    variables: {
      // Map the nested.value property from generateData's output
      previousValue: { step: generateData, path: 'nested.value' },
    }
  })
  .commit();

Type Safety

Mastra provides type safety for variable mappings when using TypeScript:

src/mastra/workflows/type-safe.ts
import { Step, Workflow } from "@mastra/core/workflows";
import { z } from "zod";
 
// Define schemas for better type safety
const triggerSchema = z.object({
  inputValue: z.string(),
});
 
type TriggerType = z.infer<typeof triggerSchema>;
 
// Step with typed context
const step1 = new Step({
  id: "step1",
  outputSchema: z.object({
    nested: z.object({
      value: z.string(),
    }),
  }),
  execute: async ({ context }) => {
    // TypeScript knows the shape of triggerData
    const triggerData = context.getStepResult<TriggerType>('trigger');
 
    return {
      nested: {
        value: `processed-${triggerData?.inputValue}`
      }
    };
  },
});
 
// Create the workflow with the schema
const workflow = new Workflow({
  name: "type-safe-workflow",
  triggerSchema,
});
 
workflow.step(step1).commit();

Best Practices

  1. Validate Inputs and Outputs: Use inputSchema and outputSchema to ensure data consistency.

  2. Keep Mappings Simple: Avoid overly complex nested paths when possible.

  3. Consider Default Values: Handle cases where mapped data might be undefined.

Comparison with Direct Context Access

While you can access previous step results directly via context.steps, using variable mappings offers several advantages:

FeatureVariable MappingDirect Context Access
ClarityExplicit data dependenciesImplicit dependencies
ReusabilitySteps can be reused with different mappingsSteps are tightly coupled
Type SafetyBetter TypeScript integrationRequires manual type assertions