Stored workflows
This feature is in beta. Breaking changes may occur without a major version bump until the API is stable.
Stored workflows are workflow definitions expressed as data instead of code. A definition is a JSON document that describes the workflow's schemas and step graph. Mastra validates the definition and registers it as a runnable workflow, then persists it in storage so it survives process restarts.
Because a definition contains no JavaScript closures, anything that can produce JSON can author a workflow: an HTTP client, an LLM, a visual editor, or your own tooling. Once registered, a stored workflow runs through the same execution API as a code-defined workflow.
When to use stored workflowsDirect link to When to use stored workflows
Use stored workflows when users, agents, visual editors, or external systems need to create workflows without changing application code or deploying again.
Keep defining workflows with createWorkflow() when the workflow belongs in your application source or needs custom step functions. Stored workflows can invoke agents, tools, and workflows that are already registered on the Mastra instance.
QuickstartDirect link to Quickstart
The following example registers a tool and invokes it from a stored workflow. It then runs the workflow. LibSQLStore persists the definition in mastra.db, so Mastra can restore it after a restart.
import { Mastra } from '@mastra/core/mastra'
import { createTool } from '@mastra/core/tools'
import { LibSQLStore } from '@mastra/libsql'
import { z } from 'zod'
const greetingTool = createTool({
id: 'create-greeting',
description: 'Create a greeting for a name',
inputSchema: z.object({
name: z.string(),
}),
outputSchema: z.object({
message: z.string(),
}),
execute: async ({ name }) => ({
message: `Hello, ${name}!`,
}),
})
const mastra = new Mastra({
storage: new LibSQLStore({
id: 'mastra-storage',
url: 'file:./mastra.db',
}),
tools: { 'create-greeting': greetingTool },
})
await mastra.addStoredWorkflow({
id: 'greeting-workflow',
description: 'Create a greeting for the supplied name',
inputSchema: {
type: 'object',
properties: {
name: { type: 'string' },
},
required: ['name'],
},
outputSchema: {
type: 'object',
properties: {
message: { type: 'string' },
},
required: ['message'],
},
graph: [
{
type: 'tool',
id: 'greet',
toolId: 'create-greeting',
},
],
})
const workflow = mastra.getWorkflow('greeting-workflow')
const run = await workflow.createRun()
const result = await run.start({
inputData: { name: 'Ada' },
})
if (result.status === 'success') {
console.log(result.result.message)
}
The workflow prints Hello, Ada!. Calling addStoredWorkflow() validates the definition before it changes storage or the live workflow registry.
The definition uses JSON Schema because it must survive a JSON round trip. The graph describes which registered components to invoke and how data moves between them. See the stored workflow definition reference for every field and graph entry.
Build and update definitionsDirect link to Build and update definitions
A definition can come from any source that produces JSON. For example, an API route can accept a definition created by a visual editor and register it directly:
const definition = await request.json()
await mastra.addStoredWorkflow(definition)
Register dependencies firstDirect link to Register dependencies first
Register referenced components on the same Mastra instance before adding the stored workflow. Agent and nested workflow entries use their intrinsic IDs. A tool entry uses its key from the Mastra tools object, so the quickstart registers the tool under create-greeting before referencing that key with toolId.
Use a mapping entry when one step's output doesn't match the next step's input. Mapping entries can read data from the workflow input and previous step results, along with workflow state and request context. The definition reference lists the supported mapping descriptors.
Replace a workflowDirect link to Replace a workflow
Add a new definition with the same id to replace the persisted definition and live registration:
await mastra.addStoredWorkflow(updatedDefinition)
New runs use the updated graph. Runs that already started continue with their original graph.
Add nested workflows togetherDirect link to Add nested workflows together
When a root workflow references helper workflows that aren't registered yet, add the full set with addStoredWorkflows():
await mastra.addStoredWorkflows([rootDefinition, helperDefinition])
Mastra validates the bundle as a unit and determines the registration order from the dependencies. If validation fails, none of the definitions are registered.
Manage definitions over HTTPDirect link to Manage definitions over HTTP
Applications don't need direct access to the Mastra instance to manage stored workflows. Use one of these interfaces:
- Client SDK workflows API: Call
upsertStoredWorkflow()from a JavaScript or TypeScript client. - Server routes: Send definitions to
POST /api/stored/workflows.
On authenticated servers, stored-workflow management requires the stored-workflows:read and stored-workflows:write permissions. Running the registered workflow requires workflows:execute.
Persist definitionsDirect link to Persist definitions
Stored definitions use the workflowDefinitions storage domain. On startup, Mastra loads active definitions from storage and registers them in dependency order.
Without a storage adapter that supports this domain, addStoredWorkflow() still registers the workflow in memory, but the definition is lost when the process restarts. See the storage reference for adapter support.