Skip to content

Storage

Neon stores files in Buckets, next to the database, through the same API as Amazon S3. A bucket belongs to a branch, so a preview branch has its own files, just as it has its own rows.

A bucket is private unless you make it public. Uploads from a browser go straight to the bucket through presigned URLs, which a Function creates for one key and a short time.

alchemy.run.ts
const uploads = yield* Neon.Bucket("Uploads", { branch, access: "private" });

access: "public_read" lets anyone download its files. cors rules let browsers upload to it directly, and forceDestroy empties it when it’s destroyed.

Bind the bucket in a Function, at the access level the Function needs:

src/api.ts
const files = yield* Neon.ReadWriteBucket(uploads);

The client reads object metadata, gets and puts objects, and creates presigned GET and PUT URLs. Neon.ReadBucket and Neon.WriteBucket give less. Provide the matching Layer, like Neon.ReadWriteBucketHttp.

Configuration a Function reads, like defaults or feature flags, fits in one file. A Neon.Object declares that file and its value, and keeps the value’s type:

alchemy.run.ts
const settings = yield* Neon.Object("Settings", {
bucket: uploads,
key: "settings.json",
value: { theme: "dark", pageSize: 20 },
});

The type is inferred from value. Pass a type argument, or an Effect Schema to check the value when it’s read.

A Function reads it with Neon.ReadObject(settings), or replaces it with Neon.WriteObject(settings), and gets the value with its type, with Neon.ReadObjectHttp or Neon.WriteObjectHttp provided.

A Function on the same branch, in production, uses credentials Neon gives it. Code anywhere else, a local run or another cloud, gets credentials Alchemy creates. To use one you’ve made yourself, pass { credential } to the binding.

Neon’s credentials work on a branch and its child branches. They can’t be limited to one bucket or one key, so a binding’s access level limits the client your code gets, not what the credential could do.