Mastra, an agent framework written in TypeScript
Mastra is a library for building LLM agents in TypeScript: you define an agent with a model, tools and memory, compose a workflow out of steps, score the answers and inspect the runs in a local panel. The current @mastra/core release is 1.61.0 from 21 August 2026, published under Apache 2.0 with one significant carve-out described below.
What Mastra gives you and what it does not
The @mastra/core package exports dozens of subpaths, but in practice you work with four concepts. An agent is an object with instructions, a model, a set of tools and optional memory. A tool is a function with a description and input and output schemas. A workflow is a graph of steps with typed data flow. A scorer is a function that grades an agent result and stores a number together with a justification.
On top of that sits a server layer. The @mastra/server package exposes agents and workflows over HTTP, and separate adapters attach it to an existing application: @mastra/hono, @mastra/express, @mastra/fastify, @mastra/koa, @mastra/nestjs, @mastra/next and @mastra/tanstack-start. If you build on Next.js, the agent does not have to be a separate process.
Mastra is not a model provider and has no inference API of its own. The AI SDK sits underneath, in three generations at once: the core declares aliases @ai-sdk/provider-v5 pointing at 2.0.3, @ai-sdk/provider-v6 at 3.0.14 and @ai-sdk/provider-v7 at 4.0.4, with a matching set of provider-utils. That helps during migrations, but it means one install carries three parallel compatibility layers.
Two core dependencies deserve a look before an audit. posthog-node at ^5.46.1 is telemetry built into the library rather than a bolt-on plugin. chat at ^4.34.0 is the Chat SDK from the vercel/chat repository, meaning Slack, Teams and Google Chat integrations get pulled in whether or not you use them. The unpacked @mastra/core 1.61.0 weighs about 65 MB against a 13.9 MB archive.
Versions, release cadence and the package family
Version 1.0.0 shipped on 20 January 2026 and 1.61.0 on 21 August. That is 61 minor bumps across 213 days, one every three and a half days on average. The registry holds 71 stable 1.x releases, which works out to one every three days. For the record, the registry lists 1505 versions of @mastra/core, but the vast majority are branch snapshots numbered 0.0.0-<branch-name>-<date> and published on every pull request.
I checked whether a deprecated version is sitting at the top of the list pretending to be current. Exactly two are marked deprecated, 0.10.13 and 0.15.0, both from the old 0.x line. Nothing in the 1.x line is withdrawn, and the alpha and beta tags point at 1.62.0-alpha.2 and 1.1.0-alpha.2 respectively.
The family is large. Querying the npm registry for @mastra returns at least 168 packages in that scope, counting only the first 250 results. The numbering is deliberately uneven: @mastra/deployer and @mastra/server move in lockstep with the core at 1.61.0, @mastra/memory is at 1.27.0, @mastra/rag at 2.6.0, @mastra/mcp at 1.17.1 and @mastra/playground-ui at 51.0.0.
I checked the peer dependency ranges separately, because that is a classic source of install conflicts. All of them take the form >=X-0 <2.0.0-0, so they are open at the top and nothing clashes against the newest core. The lower bounds do diverge: @mastra/rag, @mastra/evals and @mastra/mcp accept any 1.x core, @mastra/libsql needs at least 1.51.0, @mastra/pg at least 1.53.0 and @mastra/inngest at least 1.58.0. Pinning an older core to avoid interface churn therefore rules out the newest adapters.
The license from three sources
The mastra-ai/mastra repository has no LICENSE file. There is only LICENSE.md, identical on both the main and master branches. The LICENSE, LICENSE.MD, LICENSE.txt, LICENCE and COPYING variants all return 404, so a scanner that only looks for the extensionless name finds nothing.
The contents of that file are not plain Apache 2.0. It opens with an exclusion: everything inside any directory named ee/, including packages/core/src/auth/ee/ and packages/server/src/server/auth/ee/, falls under the license in ee/LICENSE. Only the remainder is Apache 2.0, with a copyright notice for Kepler Software, Inc.
The ee/LICENSE file exists and is titled Mastra Enterprise Edition (EE) License. It permits modifying the code for your own development and testing, while production use requires a written agreement with Kepler Software. Production means any use beyond development and testing on your own systems, and a staging environment is explicitly counted as testing. Copying, distribution and sale are forbidden.
The second source, the license field in the npm registry, simply says Apache-2.0 with no trace of that exclusion. The third source is more interesting still. The published @mastra/core 1.61.0 package contains no license file at all, because the files field in its package.json is ["dist", "CHANGELOG.md", "./**/*.d.ts"]. It does contain 66 paths under ee/ directories, among them dist/auth/ee/fga-check.js and dist/agent-builder/ee/. In other words, code covered by the commercial license ships inside a package declared as Apache 2.0, and the license text is not in the package at all.
Consistency across the family is uneven too. @mastra/inngest 1.8.7 behaves the opposite way to the core: it has no license field whatsoever, yet it ships a LICENSE.md with the full carve-out text. The same field is missing from @mastra/editor 0.14.0 and @mastra/redis-streams 0.4.0. The @mastra/memory, @mastra/rag, @mastra/evals, @mastra/mcp, @mastra/libsql packages and the mastra CLI all ship a license file correctly.
The practical takeaway: if you maintain a dependency license register, the entry "Apache-2.0" for @mastra/core is incomplete. Check whether you touch anything from @mastra/core/auth/ee or the agent builder, because that is a different legal regime.
# core and CLI
npm install @mastra/core mastra
# durable storage instead of the default process memory
npm install @mastra/libsql
# what usually follows later
npm install @mastra/memory @mastra/evals @mastra/mcp
# local studio on port 4111
npx mastra dev
# build into .mastra/output
npx mastra build
# what the license inside the package really is
npm pack @mastra/core@1.61.0
tar tzf mastra-core-1.61.0.tgz | grep -E "^package/(LICENSE|LICENCE)"Agent, tools and memory in code
The agent configuration accepts id, name, instructions, model, tools, memory, workflows, agents, scorers, inputProcessors, outputProcessors, defaultOptions and maxSteps. You create a tool with createTool and the fields id, description, inputSchema, outputSchema and execute. Since the 1.x line the first argument to execute is the input data directly rather than an object with a context field, which is the most common trap when porting older examples.
import { Mastra } from '@mastra/core'
import { Agent } from '@mastra/core/agent'
import { createTool } from '@mastra/core/tools'
import { Memory } from '@mastra/memory'
import { LibSQLStore } from '@mastra/libsql'
import { z } from 'zod'
const invoiceTool = createTool({
id: 'fetch-invoice',
description: 'Fetches an invoice by number',
inputSchema: z.object({ number: z.string() }),
outputSchema: z.object({ total: z.number(), currency: z.string() }),
requireApproval: false,
execute: async (inputData) => {
const res = await fetch(`https://erp.internal/invoices/${inputData.number}`)
return res.json()
}
})
const memory = new Memory({
storage: new LibSQLStore({ id: 'agent-memory', url: 'file:./agent-memory.db' }),
options: {
lastMessages: 20,
semanticRecall: { topK: 5, messageRange: 2, scope: 'resource' },
workingMemory: {
enabled: true,
scope: 'resource',
template: '# Customer profile\n- **Name**:\n- **Billing currency**:'
}
}
})
export const mastra = new Mastra({
agents: {
billing: new Agent({
id: 'billing',
name: 'Billing',
instructions: 'Answer questions about invoices only.',
model: 'openai/gpt-5-mini',
tools: { invoiceTool },
memory
})
},
storage: new LibSQLStore({ id: 'runs', url: 'file:./mastra-runs.db' })
})The requireApproval field takes a boolean or a function and suspends tool execution until it is approved. Together with suspendSchema and resumeSchema this gives you human-in-the-loop handling without your own task queue.
The core requires Node 22.13.0 or newer and declares a peer dependency on zod in the range ^3.25.0 || ^4.0.0. Schemas go through Standard Schema, so zod is not formally the only option, but its version is what gets checked at install time. Anyone writing purely in TypeScript gets type inference from the tool schema all the way to the workflow step result.
Workflows and scorers
You create a workflow with createWorkflow, steps with createStep, and assemble the graph with then, parallel, branch, dountil, dowhile, foreach, map, sleep, sleepUntil and waitForEvent, closing it with commit. Execution goes through createRun. Declaring a schedule automatically promotes the workflow to the evented engine.
import { createWorkflow, createStep } from '@mastra/core/workflows'
import { z } from 'zod'
const parse = createStep({
id: 'parse',
inputSchema: z.object({ raw: z.string() }),
outputSchema: z.object({ items: z.array(z.string()) }),
execute: async ({ inputData }) => ({ items: inputData.raw.split('\n') })
})
const enrich = createStep({
id: 'enrich',
inputSchema: z.object({ items: z.array(z.string()) }),
outputSchema: z.object({ enriched: z.number() }),
execute: async ({ inputData }) => ({ enriched: inputData.items.length })
})
export const ingest = createWorkflow({
id: 'ingest',
inputSchema: z.object({ raw: z.string() }),
outputSchema: z.object({ enriched: z.number() })
})
.then(parse)
.then(enrich)
.commit()Evaluation lives in @mastra/evals 1.9.0, with the subpaths ./scorers/prebuilt, ./scorers/utils and ./checks. The ready-made scorers include createAnswerRelevancyScorer, createFaithfulnessScorer, createHallucinationScorer, createToxicityScorer, createBiasScorer, createContextPrecisionScorer, createToolCallAccuracyScorerCode and createTrajectoryAccuracyScorerLLM, among others. You build your own with createScorer and the fields id, description, judge, type and prepareRun, then chain preprocess, analyze, generateScore and generateReason.
import { createScorer } from '@mastra/core/evals'
import { createFaithfulnessScorer } from '@mastra/evals/scorers/prebuilt'
import { z } from 'zod'
const hasInvoiceNumber = createScorer({
id: 'has-invoice-number',
description: 'Checks whether the answer contains the invoice number',
type: { input: z.object({ number: z.string() }), output: z.string() }
})
.analyze(({ run }) => ({ found: run.output.includes(run.input.number) }))
.generateScore(({ results }) => (results.analyze.found ? 1 : 0))
.generateReason(({ results }) =>
results.analyze.found ? 'Number present' : 'No number in the answer'
)
const faithfulness = createFaithfulnessScorer({ model: 'openai/gpt-5-mini' })Scorer results land in the same store as the runs, so the panel shows them next to the execution traces. If you already collect metrics in Langfuse or Braintrust, separate bridge packages exist for both.
Studio, run storage and the paid platform
The mastra dev command brings up the local studio on port 4111 unless the PORT variable says otherwise. The interface is bundled inside the mastra package under dist/studio, so you do not install it separately. Builds go to .mastra/output.
The key question is where runs and memory land when you configure nothing. The answer is unambiguous and visible in the code: the Mastra constructor checks the storage field, and when it is absent it creates an InMemoryStore and queues a warning that reads:
No `storage` configured on Mastra — falling back to an in-memory store. In-memory
storage is not durable: all data is lost on restart, and it is not safe for
production. Configure a persistent storage adapter (e.g. @mastra/libsql,
@mastra/pg, @mastra/cloudflare).By default there is therefore no SQLite file and nothing else on disk. Everything lives in process memory and disappears on restart. The panel is a development tool; for anything from it to survive a deployment you have to attach an adapter and run a database, which is one more moving part in the architecture.
The paid offering splits into two tracks. The self-hosted variant is free under Apache 2.0, and the paid Enterprise add-on covers RBAC, SSO, IAM and network policy, at a single flat annual fee with no per-trace metering. Those are precisely the features that sit under ee/ in the repository. The second track is the hosted Mastra Platform.
| Item | Starter | Teams |
|---|---|---|
| Monthly price | 0 USD | 250 USD |
| Observability events | 100k, then 10 USD/100k | 1M, then 8 USD/100k |
| CPU time | 24 h, then 0.35 USD/h | 250 h, then 0.25 USD/h |
| Data retention | 15 days | 6 months |
| Data egress | 10 GB, then 0.10 USD/GB | 100 GB, then 0.10 USD/GB |
| Retrieval storage | 250 MB, then 20 USD/GB | 1 GB, then 20 USD/GB |
| Gateway tokens | market rate + 5.5% | market rate + 5.5% |
The pricing page renders server-side, so it can be read without running JavaScript. Two things in it stand out. First, the LibSQL row is headed "Rows Written" yet prices the overage at 2.50 USD per million reads on Starter and 2 USD on Teams; writes and reads are two different things and the table contradicts itself. Second, the CPU add-on for the Enterprise plan is quoted at 0.00008 USD per second, which works out to 0.288 USD per hour, a figure that sits between the rates of the two cheaper plans.
The break-even point is worth computing too. Counting the observability line alone, Starter charges 10 USD per 100k events beyond the first 100k, while Teams charges 250 USD plus 8 USD per 100k beyond one million. They meet at 9 million events per month, where both come to 890 USD. Below that threshold you buy Teams for retention, SSO and CPU allowance, not for cheaper events.
Mastra against other agent frameworks
The main argument for Mastra is simple: it is a library written in TypeScript for TypeScript, not a port from Python. That argument has to be stated honestly, though, because the version that says "the competition has nothing in JavaScript" no longer holds.
| Framework | Original language | npm counterpart | State of the port |
|---|---|---|---|
| Mastra | TypeScript | @mastra/core 1.61.0 | the only implementation |
| LangGraph | Python | @langchain/langgraph 1.4.12 | active, ahead of the Python number |
| OpenAI Agents SDK | Python | @openai/agents 0.17.0 | active, still on the 0.x line |
| PydanticAI | Python | none | no port |
| CrewAI | Python | only the unofficial crewai 1.0.1 | dead since August 2024 |
| LangChain Agents | Python | @langchain/core 1.2.9 | active |
The JavaScript port of LangGraph shipped on 19 August 2026, three days before this measurement, and its version number is even higher than the Python 1.2.11. The claim about neglected ports does hold elsewhere: @instructor-ai/instructor stopped at 1.7.0 from 27 January 2025, PydanticAI has nothing on npm, and the crewai package on npm is a private implementation from the jaafarskafi1/crew-js repository, not code from the CrewAI team.
The differences are less about availability than about character. LangGraph describes a state graph and that is its only model. PydanticAI leans on typed output through validation. CrewAI organises work around roles in a team. The OpenAI Agents SDK is most convenient with a single provider. Mastra covers a wider span: agent, workflow, memory, RAG, evaluation, panel and deployer in one repository. That breadth is an advantage at the start and a burden in maintenance, because you update everything at once and releases land every three days.
The company behind the project is Kepler Software, Inc., founded in October 2024 by people previously involved with Gatsby, including Sam Bhagwat and Abhi Aiyer. The project site reports 27.4k stars on the repository. Depending on a single company is a real risk: it decides what goes under ee/, and the boundary between the open and the commercial part runs through directories rather than through separate packages.
Common mistakes
Running in production without a storage field is the most frequent one. The application starts, the agent answers, and after a container restart there are no conversation threads and no execution traces. The warning goes to the log and is easy to miss.
The second is treating Apache-2.0 from the npm registry as the full answer to the license question. The carve-out for ee/ directories lives only in LICENSE.md in the repository, and the package carries no license text at all.
The third is the version range in package.json. With 61 minor releases in seven months, writing ^1.0.0 means somebody on the team will pull a different core than the rest. Pin an exact version and raise it deliberately.
The fourth is copying old examples. The execute signature in tools changed to passing input data directly, and the panel was renamed from playground to studio, while the @mastra/playground-ui package still exists under the old name at version 51.0.0.
The fifth is mixing adapters with a pinned older core. Peer dependency floors differ per package, and @mastra/inngest will not install against a core older than 1.58.0 even if the rest of the project works. If you need durable, long-lived runs, consider the bridge to Inngest or Temporal instead, both of which have dedicated packages.
FAQ
Is Mastra fully open source?
Not entirely. Code outside directories named ee/ is Apache 2.0. The contents of ee/ directories, including packages/core/src/auth/ee/, fall under a separate Enterprise Edition license that allows development and testing but makes production use conditional on a written agreement with Kepler Software.
Where does Mastra store memory and runs by default?
Nowhere durable. Without a storage field the constructor creates an InMemoryStore and the data disappears when the process restarts. You enable durability with an adapter, for example @mastra/libsql with url: 'file:./mastra.db', @mastra/pg or @mastra/cloudflare.
Is the release cadence a problem?
It depends on your discipline. Sixty-one minor versions in 213 days is one every three and a half days, and the 1.x line has no deprecated versions. With an exact pin and deliberate upgrades this is manageable; with a ^ range you get surprises between environments.
How does Mastra differ from LangGraph?
LangGraph models a state graph and has a JavaScript port at version 1.4.12, released alongside the Python one. Mastra covers more layers at once, including memory, evaluation and a local panel, and is written purely in TypeScript, so it does not translate concepts from another language.
What does the hosted Mastra Platform cost?
The Starter plan is free with a limit of 100k observability events, 24 CPU hours and 15 days of retention. The Teams plan costs 250 USD per month for 1 million events, 250 CPU hours and six months of retention. Enterprise is custom priced.
Can you use Mastra without its server?
Yes. Agents and workflows can be called directly from application code. The @mastra/next, @mastra/hono and @mastra/nestjs adapters exist to expose them inside an existing application, not to run a separate process.
Sources: the mastra-ai/mastra repository, the @mastra/core package on npm, Mastra pricing.