Skip to Content
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(); // Register the workflow with Mastra export const mastra = new Mastra({ workflows: { workflow }, });

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();

Variables in Loops

Variables can also be passed to while and until loops. This is useful for passing data between iterations or from outside steps:

src/mastra/workflows/loop-variables.ts
// Step that increments a counter const incrementStep = new Step({ id: 'increment', inputSchema: z.object({ // Previous value from last iteration prevValue: z.number().optional(), }), outputSchema: z.object({ // Updated counter value updatedCounter: z.number(), }), execute: async ({ context }) => { const { prevValue = 0 } = context.inputData; return { updatedCounter: prevValue + 1 }; }, }); const workflow = new Workflow({ name: 'counter' }); workflow .step(incrementStep) .while( async ({ context }) => { // Continue while counter is less than 10 const result = context.getStepResult(incrementStep); return (result?.updatedCounter ?? 0) < 10; }, incrementStep, { // Pass previous value to next iteration prevValue: { step: incrementStep, path: 'updatedCounter' } } );

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 as the inputData property

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, Mastra } from "@mastra/core"; 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.inputData; 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(); // Register the workflow with Mastra export const mastra = new Mastra({ workflows: { workflow }, });

Mapping Between Steps

This example demonstrates mapping data from one step to another:

src/mastra/workflows/step-mapping.ts
import { Step, Workflow, Mastra } from "@mastra/core"; 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.inputData; 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(); // Register the workflow with Mastra export const mastra = new Mastra({ workflows: { workflow }, });

Type Safety

Mastra provides type safety for variable mappings when using TypeScript:

src/mastra/workflows/type-safe.ts
import { Step, Workflow, Mastra } from "@mastra/core"; 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(); // Register the workflow with Mastra export const mastra = new Mastra({ workflows: { workflow }, });

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