Skip to main content

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 begin
Direct 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, or aws-ap-southeast-1
  • The Neon CLI

Installation
Direct link to Installation

npm install @mastra/s3 @neon/config

Create a bucket
Direct link to Create a bucket

Declare the bucket in neon.ts at the root of your project:

neon.ts
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 variables
Direct link to Environment variables

Neon sets four variables for object storage:

VariableValue
AWS_ACCESS_KEY_IDAccess key for a branch credential
AWS_SECRET_ACCESS_KEYSecret key for that credential
AWS_ENDPOINT_URL_S3S3 endpoint of the branch
AWS_REGIONRegion 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.

Usage
Direct link to Usage

Pass the variables to S3Filesystem and assign the workspace to an agent:

src/mastra/agents/file-agent.ts
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.

Branches
Direct 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 storage
Direct 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 adapter
Direct 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 install @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',
}),
})