mrkeyoor.com_
Wed 05 Aug 05:06 UTC
npmInfraupdated 05 Aug 2026

@aws-sdk/client-s3

@aws-sdk/client-s3 is the official AWS SDK for JavaScript v3 client for S3: upload, download, list, copy, and delete objects plus bucket-level operations, with one Command class per API call. v3 split the old monolithic aws-sdk into per-service packages so you ship only the client you use, added first-class TypeScript types, and routes every call through a middleware stack. It also talks to S3-compatible stores (Cloudflare R2, MinIO, Backblaze B2) via a custom endpoint, with caveats.

Verdict

If you use S3 from Node, use this client; it is well built once the Command pattern clicks. Budget time for the generated docs, pin versions, and watch release notes, because AWS ships daily and has changed defaults inside the major before.

API stability4/5The v3 Command API has been stable since general availability in December 2020, but AWS changes behavior inside the major: the v3.729.0 default integrity checksums broke S3-compatible backends in early 2025.
Docs3/5The developer guide is decent, but the API reference is generated from service models: exhaustive, accurate, and painful to navigate. Most people end up reading TypeScript types or third-party posts to find input shapes.
Maintenance5/5Maintained by AWS with releases nearly every business day (3.1103.0, last push August 2026) and a dedicated team triaging the 140 open issues and PRs on the monorepo.
Ecosystem4/539M weekly downloads and the entire AWS tooling universe assumes it, but community material is thinner than v2 accumulated over a decade, and helpers are split across many small packages.

Use it if

  • You talk to S3 or an S3-compatible store from Node 20+ and want the client AWS itself builds, types, and supports
  • You need the full surface: multipart uploads, presigned URLs, versioning, checksums, bucket policies
  • You run on Lambda, where the v3 SDK ships inside the Node.js runtimes so the dependency is effectively free
  • You bundle for size and want per-service packages plus modular commands that tree shaking can trim
Skip it if

Setup reality

npm install @aws-sdk/client-s3 pulls a wide tree of @aws-sdk/* and @smithy/* internals and requires Node 20+. Presigned URLs and managed multipart uploads live in separate packages (@aws-sdk/s3-request-presigner, @aws-sdk/lib-storage) that you discover only when something is missing. Versions move almost daily (3.1103.0 at review time), so pin and upgrade deliberately. Credentials flow through the default provider chain, which is painless on AWS infrastructure and confusing everywhere else; local dev usually means env vars or a shared credentials file, and S3-compatible endpoints usually need forcePathStyle plus checksum overrides.

Patterns

Create a clientclient-setup

import { S3Client } from '@aws-sdk/client-s3';

const s3 = new S3Client({ region: 'us-east-1' });

Credentials come from the default provider chain (env vars, shared config, IAM role); reuse one client instead of creating one per request.

Upload an objectput-object

import { PutObjectCommand } from '@aws-sdk/client-s3';

await s3.send(new PutObjectCommand({
  Bucket: 'my-bucket',
  Key: 'notes/hello.txt',
  Body: 'hello world',
  ContentType: 'text/plain'
}));

Body accepts strings, Buffers, and streams, but a stream of unknown length fails; use Upload from @aws-sdk/lib-storage for those.

Read an object into memoryget-object

import { GetObjectCommand } from '@aws-sdk/client-s3';

const res = await s3.send(new GetObjectCommand({
  Bucket: 'my-bucket',
  Key: 'notes/hello.txt'
}));
const text = await res.Body.transformToString();

Unlike v2, Body is a stream, not a Buffer; transformToString and transformToByteArray are the easy way out for small objects.

Stream a large object to diskdownload-to-file

import { createWriteStream } from 'node:fs';
import { pipeline } from 'node:stream/promises';
import { GetObjectCommand } from '@aws-sdk/client-s3';

const res = await s3.send(new GetObjectCommand({ Bucket: 'my-bucket', Key: 'big.bin' }));
await pipeline(res.Body, createWriteStream('/tmp/big.bin'));

Pipe instead of buffering; transformToByteArray on a multi-GB object will exhaust the heap.

List all keys under a prefixlist-objects

import { paginateListObjectsV2 } from '@aws-sdk/client-s3';

for await (const page of paginateListObjectsV2(
  { client: s3 },
  { Bucket: 'my-bucket', Prefix: 'logs/' }
)) {
  for (const obj of page.Contents ?? []) console.log(obj.Key, obj.Size);
}

ListObjectsV2 returns at most 1000 keys per call; the paginator handles ContinuationToken, and Contents is undefined on empty pages.

Generate a presigned URLpresigned-url

import { GetObjectCommand } from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';

const url = await getSignedUrl(
  s3,
  new GetObjectCommand({ Bucket: 'my-bucket', Key: 'file.pdf' }),
  { expiresIn: 3600 }
);

The presigner is a separate package. Expiry caps at 7 days, and URLs signed with temporary credentials die when those credentials expire.

Upload big files with progressmultipart-upload

import { Upload } from '@aws-sdk/lib-storage';

const upload = new Upload({
  client: s3,
  params: { Bucket: 'my-bucket', Key: 'video.mp4', Body: fileStream }
});
upload.on('httpUploadProgress', (p) => console.log(p.loaded, '/', p.total));
await upload.done();

PutObject tops out at 5 GB and cannot report progress; Upload does multipart with parallel parts and handles unknown-length streams.

Check whether a key existscheck-exists

import { HeadObjectCommand } from '@aws-sdk/client-s3';

try {
  await s3.send(new HeadObjectCommand({ Bucket: 'my-bucket', Key: 'maybe.txt' }));
  // exists
} catch (err) {
  if (err.name === 'NotFound') {
    // missing
  } else throw err;
}

HeadObject throws NotFound (not NoSuchKey), and you get 403 instead of 404 when the caller lacks s3:ListBucket on the bucket.

Delete multiple objects in one calldelete-many

import { DeleteObjectsCommand } from '@aws-sdk/client-s3';

const res = await s3.send(new DeleteObjectsCommand({
  Bucket: 'my-bucket',
  Delete: { Objects: [{ Key: 'a.txt' }, { Key: 'b.txt' }] }
}));
if (res.Errors?.length) console.error(res.Errors);

Limit is 1000 keys per call, and per-key failures arrive inside a 200 response; always check the Errors array.

Point the client at R2 or MinIOs3-compatible-endpoint

import { S3Client } from '@aws-sdk/client-s3';

const s3 = new S3Client({
  region: 'auto',
  endpoint: 'https://ACCOUNT_ID.r2.cloudflarestorage.com',
  forcePathStyle: true,
  requestChecksumCalculation: 'WHEN_REQUIRED',
  responseChecksumValidation: 'WHEN_REQUIRED'
});

Since v3.729.0 the SDK sends CRC32 checksums by default, which broke several S3-compatible services; the WHEN_REQUIRED settings restore the old behavior.

Copy an object server-sidecopy-object

import { CopyObjectCommand } from '@aws-sdk/client-s3';

await s3.send(new CopyObjectCommand({
  Bucket: 'dest-bucket',
  Key: 'copy.txt',
  CopySource: '/src-bucket/original.txt'
}));

CopySource needs URL encoding when the key has special characters, and server-side copy caps at 5 GB before you need multipart copy.

Tune retries and timeoutsretry-timeout

import { S3Client } from '@aws-sdk/client-s3';
import { NodeHttpHandler } from '@smithy/node-http-handler';

const s3 = new S3Client({
  maxAttempts: 5,
  retryMode: 'adaptive',
  requestHandler: new NodeHttpHandler({
    connectionTimeout: 3000,
    requestTimeout: 30000
  })
});

Defaults are 3 attempts in standard retry mode and no overall request timeout; a hung socket waits forever unless you set one.

Alternatives

PackageRegistryPick it when
minionpmA smaller focused client for MinIO and other S3-compatible stores without the AWS dependency tree
aws4fetchnpmYou just need SigV4-signed S3 requests from browsers, Cloudflare Workers, or other edge runtimes
aws-sdknpmA legacy v2 codebase not worth migrating yet; note v2 reached end-of-support and gets no fixes