Neon Object Storage
Neon Object Storage is S3-compatible storage built into a Neon project. Buckets belong to a branch, and a child branch starts with a copy-on-write copy of its parent's files. Mastra connects to it through S3Filesystem, so agents get workspace files that branch together with the rows in your Neon Postgres database. For interface details, see WorkspaceFilesystem Interface.
Before you beginDirect link to Before you begin
You'll need:
- A Neon project in a region that supports Object Storage:
aws-us-east-1,aws-us-east-2,aws-eu-central-1, oraws-ap-southeast-1 - The Neon CLI
InstallationDirect link to Installation
- npm
- pnpm
- Yarn
- Bun
npm install @mastra/s3 @neon/config
pnpm add @mastra/s3 @neon/config
yarn add @mastra/s3 @neon/config
bun add @mastra/s3 @neon/config
Create a bucketDirect link to Create a bucket
Declare the bucket in neon.ts at the root of your project:
import { defineConfig } from '@neon/config/v1'
export default defineConfig({
buckets: {
'agent-files': {},
},
})
Buckets are private by default. Set access: 'public_read' to allow anonymous reads.
Link the directory to your Neon project, then create the bucket on the linked branch:
neon link
neon deploy
neon deploy creates the bucket and writes the branch's storage credentials to .env, or to .env.local when no .env exists. mastra dev loads either file.
To create a bucket without neon.ts, run neon bucket create agent-files, then pull the credentials yourself:
neon env pull --service object-storage
Environment variablesDirect link to Environment variables
Neon sets four variables for object storage:
| Variable | Value |
|---|---|
AWS_ACCESS_KEY_ID | Access key for a branch credential |
AWS_SECRET_ACCESS_KEY | Secret key for that credential |
AWS_ENDPOINT_URL_S3 | S3 endpoint of the branch |
AWS_REGION | Region of the project, such as us-east-2 |
A credential works on the branch it was issued for and on that branch's children. Inside a Neon Function, Neon injects these variables, so you don't need a .env file.
UsageDirect link to Usage
Pass the variables to S3Filesystem and assign the workspace to an agent:
import { Agent } from '@mastra/core/agent'
import { Workspace } from '@mastra/core/workspace'
import { S3Filesystem } from '@mastra/s3'
const workspace = new Workspace({
filesystem: new S3Filesystem({
bucket: 'agent-files',
region: process.env.AWS_REGION!,
endpoint: process.env.AWS_ENDPOINT_URL_S3,
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
}),
})
export const fileAgent = new Agent({
id: 'file-agent',
name: 'file-agent',
instructions: 'You manage files in the workspace.',
model: 'anthropic/claude-opus-5-5',
workspace,
})
Always set endpoint. Neon only accepts path-style requests, and S3Filesystem switches to them only when endpoint is set. Without it, the AWS SDK still reads AWS_ENDPOINT_URL_S3 but sends virtual-hosted requests (agent-files.<endpoint-host>), which fail TLS verification.
S3Filesystem recognizes Neon endpoints. It sets icon to 'neon' and displayName to Neon Object Storage, so Studio shows a Neon mount in the file tree.
Use prefix to give each agent, user, or thread its own folder in one bucket:
const filesystem = new S3Filesystem({
bucket: 'agent-files',
prefix: `users/${userId}`,
region: process.env.AWS_REGION,
endpoint: process.env.AWS_ENDPOINT_URL_S3,
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
})
The prefix limits which keys the filesystem addresses. It isn't an authorization boundary.
BranchesDirect link to Branches
Each branch has its own storage endpoint. When you create a branch, it starts with every bucket and file from its parent at that point in time. Writes and deletes on the child don't change the parent, and later writes on the parent don't appear in the child.
Use this to give a preview deployment, a test run, or an experimental agent a full copy of production files without copying any data:
neon checkout preview/new-tools --create
neon checkout --create creates the branch, pins it in .neon, and pulls the child branch's variables into .env, including its endpoint. The parent's credential also works on the child, but the endpoint must be the child's. Delete the branch to discard its files.
Skill blob storageDirect link to Skill blob storage
S3BlobStore holds the content-addressable skill blobs that MastraEditor produces. It takes the same connection options:
import { S3BlobStore } from '@mastra/s3'
const blobStore = new S3BlobStore({
bucket: 'agent-files',
region: process.env.AWS_REGION!,
endpoint: process.env.AWS_ENDPOINT_URL_S3,
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
})
FilesSDK adapterDirect link to FilesSDK adapter
If you use FilesSDKFilesystem, the neon adapter reads the same AWS_* variables. Install it with the AWS SDK packages it loads:
- npm
- pnpm
- Yarn
- Bun
npm install @mastra/files-sdk files-sdk @aws-sdk/client-s3 @aws-sdk/s3-presigned-post @aws-sdk/s3-request-presigner
pnpm add @mastra/files-sdk files-sdk @aws-sdk/client-s3 @aws-sdk/s3-presigned-post @aws-sdk/s3-request-presigner
yarn add @mastra/files-sdk files-sdk @aws-sdk/client-s3 @aws-sdk/s3-presigned-post @aws-sdk/s3-request-presigner
bun add @mastra/files-sdk files-sdk @aws-sdk/client-s3 @aws-sdk/s3-presigned-post @aws-sdk/s3-request-presigner
import { Workspace } from '@mastra/core/workspace'
import { FilesSDKFilesystem } from '@mastra/files-sdk'
import { Files } from 'files-sdk'
import { neon } from 'files-sdk/neon'
const workspace = new Workspace({
filesystem: new FilesSDKFilesystem({
files: new Files({ adapter: neon({ bucket: 'agent-files' }) }),
icon: 'neon',
displayName: 'Neon Object Storage',
}),
})