You can now use multiple filesystems from a single Mastra workspace with mounts. Read from restricted storage environments and write to the filesystems your team already use. Mastra supports a number of remote filesystems letting you mix-and-match to suit your needs.
By configuring multiple filesystems, your agents, tools, and workflows can read from multiple sources, combine the data, and write to a destination through the same file interface.
Before mounts, a Mastra workspace attached one filesystem at a time. Reading from one provider and writing to another meant hand-rolling the routing, or calling underlying SDKs directly. Now, all filesystems are available through the mountPath, making it easier to read and write across multiple filesystems.
Under the hood, mounts builds a CompositeFilesystem that routes each read or write to the correct provider. With a sandbox configured, Mastra also mounts each provider with FUSE (Filesystem in Userspace). Scripts and CLI tools inside the sandbox then work with the mounted files directly.
Get started
Install the filesystem providers. This example uses Amazon S3 and Google Drive:
npm install @mastra/s3 @mastra/google-drive@mastra/core@1.51.0 or later, added in PR #12851.Create a workspace with a mount for each filesystem:
import { Workspace } from "@mastra/core/workspace";
import { S3Filesystem } from "@mastra/s3";
import { GoogleDriveFilesystem } from "@mastra/google-drive";
export const s3GoogleDriveWorkspace = new Workspace({
id: "s3-google-drive-workspace",
mounts: {
"/data": new S3Filesystem({
bucket: process.env.S3_BUCKET!,
region: process.env.AWS_REGION!,
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY
}),
"/reports": new GoogleDriveFilesystem({
folderId: process.env.GOOGLE_DRIVE_FOLDER_ID!,
serviceAccount: {
clientEmail: process.env.GOOGLE_DRIVE_CLIENT_EMAIL!,
privateKey: process.env.GOOGLE_DRIVE_PRIVATE_KEY!
}
})
}
});Register the workspace on your main Mastra instance:
import { Mastra } from "@mastra/core/mastra";
import { s3GoogleDriveWorkspace } from "./workspaces/s3-google-drive-workspace";
export const mastra = new Mastra({
// ...
workspace: s3GoogleDriveWorkspace
});Accessing a filesystem from a workflow step or tool's execute function works the same way. The mountPath routes reads or writes to the configured filesystem.
const readInAppFeedbackStep = createStep({
id: "read-in-app-feedback",
// ...
execute: async ({ inputData, mastra }) => {
const fs = mastra?.getWorkspace()?.filesystem;
const mountPath = "/data"; // S3Filesystem
const entries = await fs.readdir(`${mountPath}/${inputData.year}/${inputData.month}/in-app-feedback`);
// ...
}
});
const saveToDriveStep = createStep({
id: "save-to-drive",
// ...
execute: async ({ inputData, mastra }) => {
const fs = mastra?.getWorkspace()?.filesystem;
const mountPath = "/reports"; // GoogleDriveFilesystem
await fs.writeFile(`${mountPath}/${inputData.filename}`, buffer);
// ...
}
});Errors
Errors from the underlying provider surface as typed exceptions you can catch:
import { FileNotFoundError } from "@mastra/core/workspace";
// ...
try {
const entries = await fs.readdir(mountPath);
// ...
} catch (error) {
if (error instanceof FileNotFoundError) {
// handle a missing path
}
throw error;
}onMount
When using a sandbox, the onMount callback fires for every configured filesystem letting you inspect the details for each one:
import { LocalSandbox, Workspace } from "@mastra/core/workspace";
export const s3GoogleDriveWorkspace = new Workspace({
// ...
mounts: {
/* ... */
},
sandbox: new LocalSandbox({ workingDirectory: "workspace" }),
onMount: async ({ filesystem, mountPath, config }) => {
if (!config) {
console.log(`no mount config for ${filesystem.name} at ${mountPath}`);
return;
}
console.log("mountPath:", mountPath);
console.log("filesystem:", filesystem.name);
console.log("config:", config.type);
}
});onMount only fires when a sandbox is invoked. Reads and writes to filesystems typically don't require a sandbox.
For more information and full configuration options, see:
