Datasets
Added in: @mastra/core@1.4.0
Datasets are collections of test cases that you run experiments against to measure how well your agents and workflows perform. Each mutation creates a new version, so you can reproduce past experiments exactly. Pair datasets with scorers to track quality across prompts, models, or code changes.
UsageDirect link to Usage
Configure storageDirect link to Configure storage
Configure storage in your Mastra instance. Datasets require a storage adapter that provides the datasets domain:
import { Mastra } from '@mastra/core'
import { LibSQLStore } from '@mastra/libsql'
export const mastra = new Mastra({
storage: new LibSQLStore({
id: 'my-store',
url: 'file:./mastra.db',
}),
})
Accessing the datasets APIDirect link to Accessing the datasets API
All dataset operations are available through mastra.datasets:
const datasets = mastra.datasets
// Create a dataset
const dataset = await datasets.create({ name: 'my-dataset' })
// Retrieve an existing dataset
const existing = await datasets.get({ id: 'dataset-id' })
// List all datasets
const { datasets: all } = await datasets.list()
Visit the DatasetsManager reference for the full list of methods.
StudioDirect link to Studio
You can also manage datasets in Studio. After opening Studio, select Datasets from the sidebar to see all your available datasets or create a new one.
To get started, select Create Dataset and set a name, description, and optional schemas. After confirming, you'll see the dataset details page with two tabs: Items and Experiments.
In the Items view you can add, update, and delete items, and view version history. Select Add Item to insert a new item with JSON editors for input and ground truth. From this view you can also import items in bulk from a CSV or JSON file. When importing, map each column to the corresponding dataset field.
Select Versions to see the full history of changes to the dataset. After selecting Compare Versions, choose any two versions and select Compare to see a side-by-side diff of all items that were added, changed, or removed between those versions.
Creating a datasetDirect link to Creating a dataset
Call create() with a name and optional description:
import { mastra } from '../index'
const dataset = await mastra.datasets.create({
name: 'translation-pairs',
description: 'English to Spanish translation test cases',
})
console.log(dataset.id) // auto-generated UUID
Defining schemasDirect link to Defining schemas
You can enforce the shape of input and groundTruth by passing a Standard JSON Schema (Zod, Valibot, ArkType, etc.) when creating the dataset:
import { z } from 'zod'
import { mastra } from '../index'
const dataset = await mastra.datasets.create({
name: 'translation-pairs',
inputSchema: z.object({
text: z.string(),
sourceLang: z.string(),
targetLang: z.string(),
}),
groundTruthSchema: z.object({
translation: z.string(),
}),
})
Items that don't match the schema are rejected at insert time.
Adding itemsDirect link to Adding items
Use addItem() for a single item or addItems() to insert in bulk:
// Single item
await dataset.addItem({
input: { text: 'Hello', sourceLang: 'en', targetLang: 'es' },
groundTruth: { translation: 'Hola' },
})
// Bulk insert
await dataset.addItems({
items: [
{
input: { text: 'Goodbye', sourceLang: 'en', targetLang: 'es' },
groundTruth: { translation: 'Adiós' },
},
{
input: { text: 'Thank you', sourceLang: 'en', targetLang: 'es' },
groundTruth: { translation: 'Gracias' },
},
],
})
Validate snapshot artifactsDirect link to Validate snapshot artifacts
Use createDatasetSnapshot() and parseDatasetSnapshot() from @mastra/core/datasets to create and validate a versioned JSON artifact. These are pure format utilities: they don't export from storage, import a dataset, allocate portable identities, or change Studio's JSON importer.
import { createDatasetSnapshot, parseDatasetSnapshot } from '@mastra/core/datasets'
const snapshot = createDatasetSnapshot({
formatVersion: 1,
datasetIdentity: '00000000-0000-4000-8000-000000000001',
configuration: { name: 'translation-pairs' },
items: [
{
itemIdentity: '00000000-0000-4000-8000-000000000002',
createdAt: '2026-09-01T09:00:00.123Z',
updatedAt: '2026-09-10T10:00:00.456Z',
payload: { input: 'Hello', groundTruth: 'Hola', scorerIds: [] },
},
],
provenance: {
exportedAt: '2026-09-11T12:00:00Z',
sourceDatasetId: 'dev-translations',
itemVersion: 1,
configurationBasis: 'export-time',
},
})
const parsed = parseDatasetSnapshot(JSON.stringify(snapshot))
console.log(parsed.digest === snapshot.digest) // true
The artifact includes configuration, complete authored item payloads, portable UUIDs, and provenance. Supply stable lowercase UUIDs rather than generating new identities on every serialization. Authored externalId values are separate from portable identity and remain unchanged. Absent, null, and empty overrides remain distinct. Omit optional properties rather than assigning undefined. Dataset descriptions also preserve absent, null, and string values.
Each item requires createdAt and updatedAt as UTC ISO 8601 strings with millisecond precision, matching Date.toISOString() for four-digit years. Both timestamps are included in the integrity digest. They represent the item's actual dates, not transfer provenance: an importer must preserve them as the destination item's createdAt and updatedAt, rather than replacing them with import time. An importer records transfer time in a separate receipt, while later edits in the destination update updatedAt normally. Implementing storage import remains outside the scope of these format helpers.
This preservation applies to the supplied artifact content. Storage adapters may already have normalized values before capture. Format validation neither recovers those distinctions nor guarantees that ordinary dataset CRUD operations can restore them.
Both helpers throw a Zod validation error for invalid content or size options. datasetSnapshotContentSchema.safeParse() validates unsigned content and identity uniqueness, while datasetSnapshotSchema.safeParse() additionally checks the digest. These schemas don't enforce a byte limit. Use parseDatasetSnapshot() for untrusted text to enforce a size budget before parsing and reject duplicate JSON property names.
The helpers default to a 4 MiB UTF-8 budget (DATASET_SNAPSHOT_DEFAULT_MAX_BYTES). Pass { maxBytes } as the second argument to either helper to raise or lower it. The value must be a positive safe integer. This is an operational limit, not part of the v1 format or digest. For example, parse the snapshot above with an 8 MiB budget:
const parsedWithLargerBudget = parseDatasetSnapshot(JSON.stringify(snapshot), {
maxBytes: 8 * 1024 * 1024,
})
createDatasetSnapshot() measures the complete artifact's compact JSON.stringify() output, including the digest. parseDatasetSnapshot() measures the supplied text, including whitespace. Pretty-printing can exceed a budget that accepts the compact artifact. Neither helper truncates data. During creation, validation, cloning, and hashing happen before the byte-size check. Because shared references can expand during JSON serialization, the budget limits output size without bounding CPU time or peak memory. When adding a transport, configure HTTP request-body limits separately from this budget.
Artifacts have a maximum nesting depth of 100, measured from the envelope root. They reject unknown structural fields, duplicate item identities or non-null externalId values, non-JSON values, and invalid Unicode. Programmatic content must use plain objects and standard arrays, not class instances or array subclasses. Arbitrary JSON inside authored payloads remains intact, including trajectory expectations. Dataset schemas are preserved as JSON objects, but these utilities don't compile them or validate items against them. Destination authorization, reference resolution, and storage support also require separate preflight checks.
The digest is a lowercase hexadecimal SHA-256 hash of the RFC 8785 canonical JSON representation of the envelope without digest. Items are sorted by portable identity for hashing, while authored array order is preserved. Provenance is covered, so a new capture time changes the digest. A matching digest verifies integrity but doesn't establish authorship or approval. Review sensitive data before creating an artifact, as the utilities don't redact it.
Updating, deleting, and purging itemsDirect link to Updating, deleting, and purging items
updateItem(), deleteItem(), and deleteItems() create new dataset versions as they modify or remove items:
await dataset.updateItem({
itemId: 'item-abc-123',
groundTruth: { translation: '¡Hola!' },
})
await dataset.deleteItem({ itemId: 'item-abc-123' })
await dataset.deleteItems({ itemIds: ['item-1', 'item-2'] })
Deleting an item hides it from the current dataset version but retains its content in historical rows and the deletion tombstone. Use purgeItem() to redact the item's stored content across its existing history:
await dataset.purgeItem({ itemId: 'item-abc-123' })
Purging replaces content in every historical row and deletion tombstone with redacted values, and scrubs linked experiment-result payloads, tags, and comments. Later experiment-result submissions for the item are also stored with redacted content.
Purge serializes or conflicts with concurrent dataset item writers without guaranteeing which operation completes first. If a mutating updateItem() call loses the race, it re-reads the purge marker and rejects with DATASET_ITEM_PURGED. deleteItem() remains idempotent, and any deletion tombstone created during the race stays redacted.
Normal item mutations use Slowly Changing Dimension Type 2 (SCD-2) versioning. Permanent purge intentionally overrides historical immutability for erasure while preserving item identity and the dataset version timeline. It doesn't create a dataset version and can't be undone. Experiment counters and review status are also preserved. Avoid storing sensitive data in externalId, which remains unchanged as the item's identity key.
MongoDB storage requires a replica set or sharded deployment with transaction support for this operation. Purging fails before changing data when MongoDB transactions aren't available.
Listing and searching itemsDirect link to Listing and searching items
listItems() supports pagination and full-text search:
// Paginated list
const { items, pagination } = await dataset.listItems({
page: 0,
perPage: 50,
})
// Full-text search
const { items: matches } = await dataset.listItems({
search: 'Hello',
})
// Search and pagination can be combined with a specific version
const { items: v2Matches } = await dataset.listItems({
version: 2,
search: 'Hello',
page: 0,
perPage: 50,
})
// Version-only snapshot returns a bare DatasetItem[]
const v2Items = await dataset.listItems({ version: 2 })
VersioningDirect link to Versioning
Adding, updating, or deleting dataset items bumps the dataset version. Purging an item's stored content with purgeItem() doesn't create a new version. This lets you pin experiments to a specific snapshot of the data while erasing sensitive content without changing the version history.
Listing versionsDirect link to Listing versions
Use listVersions() to see the paginated history of versions:
const { versions, pagination } = await dataset.listVersions()
for (const v of versions) {
console.log(`Version ${v.version} — created ${v.createdAt}`)
}
Viewing item historyDirect link to Viewing item history
See how a specific item changed across versions by calling getItemHistory() with the itemId:
const history = await dataset.getItemHistory({ itemId: 'item-abc-123' })
for (const row of history) {
console.log(`Version ${row.datasetVersion}`, row.input, row.groundTruth)
}
Pinning to a versionDirect link to Pinning to a version
Fetch the exact items that existed at a past version:
const items = await dataset.listItems({ version: 2 })
You can also pin experiments to a version, see running experiments. Visit the Dataset reference for the full list of methods and parameters.