Workflow Runtime Variables
Mastra provides a powerful dependency injection system that enables you to configure your workflows and steps with runtime variables. This feature is essential for creating flexible and reusable workflows that can adapt their behavior based on runtime configuration.
Overview
The dependency injection system allows you to:
- Pass runtime configuration variables to workflows through a type-safe container
- Access these variables within step execution contexts
- Modify workflow behavior without changing the underlying code
- Share configuration across multiple steps within the same workflow
Basic Usage
const myWorkflow = mastra.getWorkflow("myWorkflow");
const { runId, start, resume } = myWorkflow.createRun();
// Define your container's type structure
type WorkflowContainer = {
multiplier: number;
}
const container = new Container<WorkflowContainer>();
container.set("multiplier", 5);
// Start the workflow execution with container
await start({
triggerData: { inputValue: 45 },
container
});
Using with REST API
Here’s how to dynamically set a multiplier value from an HTTP header:
src/index.ts
import { Mastra } from "@mastra/core";
import { Container } from "@mastra/core/di";
import { workflow as myWorkflow } from "./workflows";
// Define container type with clear, descriptive types
type WorkflowContainer = {
multiplier: number;
}
export const mastra = new Mastra({
workflows: {
myWorkflow,
},
server: {
middleware: [
async (c, next) => {
const multiplier = c.req.header("x-multiplier");
const container = c.get<WorkflowContainer>("container");
// Parse and validate the multiplier value
const multiplierValue = parseInt(multiplier || "1", 10);
if (isNaN(multiplierValue)) {
throw new Error("Invalid multiplier value");
}
container.set("multiplier", multiplierValue);
await next(); // Don't forget to call next()
},
],
},
});
Creating Steps with Variables
Steps can access container variables and must conform to the workflow’s container type:
import { Step } from "@mastra/core/workflow";
import { z } from "zod";
// Define step input/output types
interface StepInput {
inputValue: number;
}
interface StepOutput {
incrementedValue: number;
}
const stepOne = new Step({
id: "stepOne",
description: "Multiply the input value by the configured multiplier",
execute: async ({ context, container }) => {
try {
// Type-safe access to container variables
const multiplier = container.get("multiplier");
if (multiplier === undefined) {
throw new Error("Multiplier not configured in container");
}
// Get and validate input
const inputValue = context.getStepResult<StepInput>('trigger')?.inputValue;
if (inputValue === undefined) {
throw new Error("Input value not provided");
}
const result: StepOutput = {
incrementedValue: inputValue * multiplier,
};
return result;
} catch (error) {
console.error(`Error in stepOne: ${error.message}`);
throw error;
}
},
});
Error Handling
When working with runtime variables in workflows, it’s important to handle potential errors:
- Missing Variables: Always check if required variables exist in the container
- Type Mismatches: Use TypeScript’s type system to catch type errors at compile time
- Invalid Values: Validate variable values before using them in your steps
// Example of defensive programming with container variables
const multiplier = container.get("multiplier");
if (multiplier === undefined) {
throw new Error("Multiplier not configured in container");
}
// Type and value validation
if (typeof multiplier !== "number" || multiplier <= 0) {
throw new Error(`Invalid multiplier value: ${multiplier}`);
}
Best Practices
- Type Safety: Always define proper types for your container and step inputs/outputs
- Validation: Validate all inputs and container variables before using them
- Error Handling: Implement proper error handling in your steps
- Documentation: Document the expected container variables for each workflow
- Default Values: Provide sensible defaults when possible