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:
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:
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:
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:
- Identifying the source step specified in the
step
property - Retrieving the output from that step
- Navigating to the specified property using the
path
- 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:
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:
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:
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
-
Validate Inputs and Outputs: Use
inputSchema
andoutputSchema
to ensure data consistency. -
Keep Mappings Simple: Avoid overly complex nested paths when possible.
-
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:
Feature | Variable Mapping | Direct Context Access |
---|---|---|
Clarity | Explicit data dependencies | Implicit dependencies |
Reusability | Steps can be reused with different mappings | Steps are tightly coupled |
Type Safety | Better TypeScript integration | Requires manual type assertions |