s3nd

Installation

Install s3nd and give it credentials.

npm install s3nd
pnpm add s3nd
yarn add s3nd
bun add s3nd

That is the server-side package. Depending on what you are building you may want one of the others instead, or as well:

PackageInstall it when
s3ndYour server talks to the bucket. Brings the AWS SDK.
@s3nd/reactYour React app sends or receives transfers. Never pulls the SDK.
@s3nd/cliYou want the s3nd command. Install it globally.
@s3nd/protocolYou are writing a client or server for a runtime the others miss.

@s3nd/react depends on @s3nd/protocol, never on s3nd, so a front-end install does not drag a storage client into node_modules.

Node 20 or later — that is what AWS SDK v3 requires. @aws-sdk/client-s3 and @aws-sdk/s3-request-presigner come along as dependencies, so there is nothing else to install and no peer dependency to satisfy.

The package ships ESM and CommonJS builds with TypeScript declarations for both, so import and require both work. It runs on your server only — there is no browser build, and no code path that would put credentials in front of a user.

Credentials

s3nd never asks for credentials it does not need. Pass them explicitly:

import { createBucket } from 's3nd'

const store = createBucket({
  bucket: 'my-bucket',
  region: 'eu-west-3',
  credentials: {
    accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
    secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
  },
})

Or omit them and let the AWS default provider chain do its job — environment variables, the shared config file, an EC2 instance role, an ECS task role, a Lambda execution role:

const store = createBucket({ bucket: 'my-bucket', region: 'eu-west-3' })

On a managed runtime, the role is the better answer: nothing to rotate and nothing to leak.

Environment variables

Every option except client falls back to an environment variable, so with S3_BUCKET and the usual AWS_* variables set, this is enough:

const store = createBucket()

See Configuration for the full list.

IAM permissions

The minimum policy for a bucket used by s3nd:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["s3:PutObject", "s3:GetObject", "s3:DeleteObject"],
      "Resource": "arn:aws:s3:::my-bucket/*"
    }
  ]
}

getUrl() needs no extra permission: presigning is a local computation over the credentials you already hold. The URL it produces grants exactly the s3:GetObject your own credentials have.

Snapshots need nothing beyond these three. putSnapshot() is a PutObject, getSnapshot() is a GetObject, and the conditional writes ride along as headers on the same call.

On this page