plugins/neon-postgres/skills/neon-object-storage/SKILL.md
S3-compatible object storage that branches with your Neon project, so files and the database stay in sync across every branch. Use when a user wants object storage, a bucket, blob/file storage, or somewhere to put uploads, images, documents, avatars, or user-generated files for their app or agent — especially when they already use (or are setting up) Neon Postgres and don't want to add a separate storage provider like AWS S3, Cloudflare R2, or Supabase Storage. Triggers include "object storage", "bucket", "blob storage", "file storage", "store uploads/images/files", "S3-compatible storage", "presigned URL", "where do I put files", "Neon Object Storage", "Neon Storage", and "storage that branches with my database".
npx skillsauth add neondatabase/agent-skills neon-object-storageInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
FIRST: Use the parent neon skill for a Neon platform overview, getting started with Neon, Neon development best practices, and more.
If the neon skill is not installed, fetch it from https://neon.com/docs/ai/skills/neon/SKILL.md or install it with:
npx skills add neondatabase/agent-skills --skill neon
This is a public beta feature and only available in us-east-2.
Neon Object Storage is S3-compatible object storage that branches with your projects: every branch gets its own isolated storage state, so files and database rows stay in sync across dev, preview, staging, and production.
Use this skill to help the user store and serve files that branch alongside their database. Deliver a working bucket and upload/download flow, a branch-aware S3 client wired to the injected env vars, or a precise answer from the official Neon docs.
Reach for Neon Object Storage when the user needs to store files (images, uploads, generated assets, documents, backups) and any of the following are true:
boto3, the AWS CLI, and presigned URLs all work — reliable and familiar, with no proprietary client.If the user has no Neon project, isn't on Postgres, and just needs a standalone CDN-backed asset store, a dedicated object store may fit better — but the moment branch-consistent files + rows matter, this is the reason to use it.
boto3, the AWS CLI, and presigned URLs. Path-style addressing and SigV4 only.private buckets require a credential for every operation; public_read buckets allow anonymous reads with authenticated writes.Check this precondition before setting anything up: Neon Object Storage is a public beta feature available only on new projects in the us-east-2 region. Confirm the user's Neon project is a new project in us-east-2 before proceeding; it can't be enabled on existing projects.
Object storage is part of the neon.ts infrastructure-as-code config (see the neon skill for the branch-first workflow, link/checkout, and neon.ts basics). Declare buckets under preview.buckets, keyed by bucket name:
// neon.ts
import { defineConfig } from "@neon/config/v1";
export default defineConfig({
preview: {
buckets: {
images: {}, // private by default
"public-assets": { access: "public_read" },
},
},
});
Provision the declared buckets on the linked branch:
neon deploy # alias for `neon config apply`
neon.ts)The preview.buckets block above is part of neon.ts, Neon's infrastructure-as-code file — one TypeScript file declares your buckets alongside every other service the branch should have (see the neon skill for the full reference). Reconcile the declaration against a branch the Terraform way:
neon config status # print the branch's live config (which buckets exist)
neon config plan # dry-run diff of what apply would change
neon config apply # create the declared buckets (neon deploy is an alias)
Buckets are branch-scoped: when a neon.ts is present, neon checkout applies the policy as it creates a branch, so a fresh preview/CI branch comes up with its buckets already provisioned (and copy-on-write objects inherited from the parent). Checking out an existing branch doesn't reconcile it — run neon deploy to apply changes. Provisioning (config apply / deploy), link, and checkout also pull the branch's S3 credentials into your local .env.local, so the same env pull step shown below happens for you on those commands.
When preview.buckets is declared, Neon injects AWS-standard S3 env vars so the AWS SDKs work from the environment with zero extra config. Inside a deployed Neon Function these are injected automatically; locally, pull them onto disk (or inject them at runtime) via the CLI:
neon env pull # writes the branch's vars into .env (or .env.local)
# or, without writing a file, inject at runtime:
neon-env run -- <your dev command>
| Variable | Meaning |
| ----------------------- | --------------------------------------------------- |
| AWS_ACCESS_KEY_ID | S3 Access Key ID (the branch credential's token id) |
| AWS_SECRET_ACCESS_KEY | S3 Secret Access Key |
| AWS_ENDPOINT_URL_S3 | Branch S3 endpoint URL |
| AWS_REGION | Region, e.g. us-east-2 |
Because the names are AWS-standard, the AWS SDK picks up the credentials, endpoint, and region from the environment automatically. Credentials are branch-scoped and valid for that branch and all its descendants.
For typed, validated access to these credentials instead of reading process.env directly, pass the same neon.ts config object to parseEnv from @neon/env — it returns an env.storage namespace (accessKeyId, secretAccessKey, endpoint, region) derived from your config. See the neon skill.
The simplest, most portable way to read and write objects is the Files SDK with its neon adapter — a small, unified storage API (upload, download, url, list, exists, copy, delete, signedUploadUrl) over web-standard I/O. It uses the AWS S3 client under the hood, configured appropriately for Neon, and relabels errors as Neon error — so there's nothing to misconfigure. Reach for this first.
Install it alongside the AWS S3 peer dependencies the adapter uses internally:
npm install files-sdk @aws-sdk/client-s3 @aws-sdk/s3-presigned-post @aws-sdk/s3-request-presigner
The adapter resolves its endpoint, region, and credentials from the same injected AWS_* env vars — pass only the bucket name:
import { Files } from "files-sdk";
import { neon } from "files-sdk/neon";
const files = new Files({ adapter: neon({ bucket: "images" }) });
// Upload — body may be a Buffer, Uint8Array, Blob, File, ReadableStream, or string
await files.upload("generated/cat.jpg", fileBuffer, { contentType: "image/jpeg" });
// Download
const file = await files.download("generated/cat.jpg");
const bytes = new Uint8Array(await file.arrayBuffer());
// Presigned GET — share without exposing credentials (defaults to a 1h expiry)
const url = await files.url("generated/cat.jpg", { expiresIn: 3600 });
// Plus: files.exists(), files.list({ prefix }), files.copy(), files.delete(), files.signedUploadUrl()
Swap the adapter import (files-sdk/s3, files-sdk/r2, files-sdk/gcs, …) and the rest of your code is unchanged.
Neon speaks the S3 API directly, so you can drop down to the AWS SDK whenever you prefer the native client or already depend on it. The credentials, endpoint, and region are read from the standard AWS env chain, so the only setting you pass is forcePathStyle: true — Neon requires path-style addressing, so the S3 client must set it:
import { S3Client } from "@aws-sdk/client-s3";
const s3 = new S3Client({
forcePathStyle: true, // required: Neon uses path-style addressing
});
Then upload, download, and presign with the raw command objects:
import { PutObjectCommand, GetObjectCommand } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
const BUCKET = "images";
// Upload
await s3.send(
new PutObjectCommand({
Bucket: BUCKET,
Key: "generated/cat.jpg",
Body: fileBuffer,
ContentType: "image/jpeg",
}),
);
// Download
const res = await s3.send(
new GetObjectCommand({ Bucket: BUCKET, Key: "generated/cat.jpg" }),
);
const bytes = await res.Body?.transformToByteArray();
// Presigned GET — share without exposing credentials
const url = await getSignedUrl(
s3,
new GetObjectCommand({ Bucket: BUCKET, Key: "generated/cat.jpg" }),
{ expiresIn: 3600 },
);
The canonical pattern: an agent generates an image → PutObject into the images bucket → a row is inserted in Postgres → a presigned URL is returned on read. Store the bucket key (not the bytes) in a Postgres column, and presign on read. Because both the row and the object live on the same branch, they branch together and never drift.
neon also has first-class bucket/object commands (neon bucket create|list|delete, neon bucket object put|get|list|delete) for scripting and one-off operations.
The Neon documentation is the source of truth and Object Storage is evolving rapidly, so always verify against the official docs. Any doc page can be fetched as markdown by appending .md to the URL or by requesting Accept: text/markdown. Find the right page from the docs index (https://neon.com/docs/llms.txt) and the changelog announcements.
neon adapter)tools
Overview of the Neon platform for apps and agents, spanning Postgres, Auth, the Data API, Object Storage, Compute Functions, and the AI Gateway. Start here to route to the right Neon skill, set up the CLI or MCP server, and follow the branch-first workflow. Use when "Neon" is mentioned, or when any of its individual capabilities are the trigger: "object storage" or "S3", "buckets", "serverless functions", "AI gateway", "call an LLM", "postgres", "database", or "backend".
tools
Guides and best practices for working with Neon Serverless Postgres. Covers setup, connection methods and drivers, pooled vs direct connections, branching, autoscaling, scale-to-zero, instant restore, read replicas, connection pooling, IP allow lists, and logical replication. Use when users ask about "Neon setup", "connect to Neon", "Neon project", "DATABASE_URL", "serverless Postgres", "Neon CLI", "neon", "Neon MCP", "Neon Auth", "@neondatabase/serverless", "@neondatabase/neon-js", "scale to zero", "Neon autoscaling", "Neon read replica", or "Neon connection pooling".
development
Diagnose and fix excessive Postgres egress (network data transfer) in a codebase. Use when a user mentions high database bills, unexpected data transfer costs, network transfer charges, egress spikes, "why is my Neon bill so high", "database costs jumped", SELECT * optimization, query overfetching, reduce Neon costs, optimize database usage, or wants to reduce data sent from their database to their application. Also use when reviewing query patterns for cost efficiency, even if the user doesn't explicitly mention egress or data transfer.
tools
Choose and create the right Neon branch type for testing and development. Use when users ask about Neon branching, migration testing with real data, isolated test environments, schema-only branch workflows for sensitive data, resetting a branch from its parent, branch expiration and CI/CD branch lifecycles, or branch creation via Neon CLI or Neon MCP. Triggers include "Neon branch", "test migrations safely", "branch production data", "schema-only branch", "reset branch", "branch per PR" and "sensitive data testing".