Skip to content

AWS.S3 reference

Source: src/AWS/S3/AbortMultipartUpload.ts

Runtime binding for s3:AbortMultipartUpload.

Discards an in-progress multipart upload and frees the storage its parts consume — abandoned uploads keep billing until aborted (or expired by a lifecycle rule). Provide the implementation with Effect.provide(AWS.S3.AbortMultipartUploadHttp).

// init — bind the operation to the bucket
const abortUpload = yield* AWS.S3.AbortMultipartUpload(bucket);
// runtime — clean up if the part-upload pipeline fails
yield* uploadAllParts.pipe(
Effect.tapError(() =>
abortUpload({ Key: "backups/archive.tar", UploadId }),
),
);

Source: src/AWS/S3/Bucket.ts

An S3 bucket for storing objects in AWS.

A bucket name is auto-generated from the app, stage, and logical ID unless you provide one explicitly via bucketName. Enable forceDestroy to allow Alchemy to empty the bucket before deleting it.

Basic Bucket

import * as S3 from "alchemy/AWS/S3";
const bucket = yield* S3.Bucket("my-bucket", {});

Bucket with a custom name

const bucket = yield* S3.Bucket("my-bucket", {
bucketName: "my-company-assets",
});

Bucket with force destroy

const bucket = yield* S3.Bucket("my-bucket", {
forceDestroy: true,
});

Versioning and encryption

const bucket = yield* S3.Bucket("my-bucket", {
versioning: "Enabled",
encryption: { sseAlgorithm: "AES256" },
});

Block all public access

const bucket = yield* S3.Bucket("my-bucket", {
publicAccessBlock: {
blockPublicAcls: true,
ignorePublicAcls: true,
blockPublicPolicy: true,
restrictPublicBuckets: true,
},
});

CORS and lifecycle rules

const bucket = yield* S3.Bucket("my-bucket", {
cors: [
{
AllowedMethods: ["GET"],
AllowedOrigins: ["*"],
AllowedHeaders: ["*"],
MaxAgeSeconds: 3000,
},
],
lifecycleRules: [
{
ID: "expire-old",
Status: "Enabled",
Filter: { Prefix: "logs/" },
Expiration: { Days: 30 },
},
],
});

Static website hosting

const bucket = yield* S3.Bucket("my-bucket", {
objectOwnership: "BucketOwnerPreferred",
website: {
indexDocument: { suffix: "index.html" },
errorDocument: { key: "error.html" },
},
});
const bucket = yield* S3.Bucket("my-bucket", {});

The default is AES256 encryption with S3-managed keys, bucket keys disabled, and no encryption types blocked. Requests may explicitly supply their own encryption keys (SSE-C); permitting SSE-C does not change the default encryption used by requests without those keys.

Bucket: Blocking Customer-Provided Encryption Keys

Section titled “Bucket: Blocking Customer-Provided Encryption Keys”
const bucket = yield* S3.Bucket("my-bucket", {
encryption: {
sseAlgorithm: "AES256",
blockedEncryptionTypes: ["SSE-C"],
},
});

blockedEncryptionTypes lists encryption types to reject. This blocks new writes using customer-provided keys while retaining AES256 default encryption. Existing encrypted objects are unchanged.

Remove the block to restore the default

const bucket = yield* S3.Bucket("my-bucket", {
encryption: {
sseAlgorithm: "AES256",
blockedEncryptionTypes: ["SSE-C"],
},
});

Redeploy the same logical resource after removing the property. Alchemy resets the blocklist to its default, [], so SSE-C writes are permitted. It updates AWS rather than preserving the previously configured block.

Set the default blocklist explicitly

const bucket = yield* S3.Bucket("my-bucket", {
encryption: {
sseAlgorithm: "AES256",
blockedEncryptionTypes: [],
},
});

[] and omission have the same desired state: no encryption types blocked. The provider sends AWS’s NONE value when it needs to reset a restriction. Redeploying unchanged code also repairs externally modified restrictions.

const bucket = yield* S3.Bucket("my-bucket", {
encryption: {
sseAlgorithm: "aws:kms",
kmsMasterKeyId: "arn:aws:kms:us-west-2:123456789012:key/12345678-1234-1234-1234-123456789012",
bucketKeyEnabled: true,
blockedEncryptionTypes: ["SSE-C"],
},
});

Omitting encryption resets every setting to the defaults: AES256, no custom KMS key, bucket keys disabled, and an empty blocklist. Previously configured KMS encryption is also reset; existing objects are not re-encrypted.

Bind S3 operations in the init phase and use them in runtime handlers. Bindings inject the bucket name and grant scoped IAM permissions automatically.

Read and write objects

// init
const getObject = yield* S3.GetObject(bucket);
const putObject = yield* S3.PutObject(bucket);
return {
fetch: Effect.gen(function* () {
// runtime
yield* putObject({
Key: "hello.txt",
Body: "Hello, World!",
ContentType: "text/plain",
});
const response = yield* getObject({ Key: "hello.txt" });
return HttpServerResponse.text("OK");
}),
};

Delete an object

// init
const deleteObject = yield* S3.DeleteObject(bucket);

Subscribe to bucket events from the init phase. The runtime binding creates the delivery permissions: Lambda invokes directly, while servers consume an SQS queue.

Notification bindings reconcile Lambda, SQS, SNS, and EventBridge destinations. Removing a binding removes the targets Alchemy manages while preserving external configurations, including identical pre-existing targets. EventBridge ownership is recorded in a reserved bucket tag so interrupted deployments can recover.

// init
yield* S3.consumeBucketEvents(bucket, {
events: ["s3:ObjectCreated:*"],
}, (stream) =>
stream.pipe(
Stream.runForEach((event) =>
Effect.log(`New object: ${event.key}`),
),
),
);

Source: src/AWS/S3/BucketEventSource.ts

Event source that streams a bucket’s notifications (object created, object removed, …) into the host Lambda Function. Usually consumed through the consumeBucketEvents helper, which provisions the bucket-notification subscription at deploy time and registers the stream handler at runtime. Provide the implementation with Effect.provide(Lambda.BucketEventSource).

BucketEventSource: Consuming Bucket Events

Section titled “BucketEventSource: Consuming Bucket Events”
export default MyFunction.make(
{ main: import.meta.url },
Effect.gen(function* () {
const bucket = yield* AWS.S3.Bucket("UploadsBucket");
const putObject = yield* AWS.S3.PutObject(bucket);
// filter to `incoming/` so the derived `processed/` write does not
// re-trigger the subscription
yield* AWS.S3.consumeBucketEvents(
bucket,
{ events: ["s3:ObjectCreated:*"], prefix: "incoming/" },
(stream) =>
stream.pipe(
Stream.runForEach((event) =>
putObject({
Key: `processed/${event.key.slice("incoming/".length)}`,
Body: JSON.stringify({ key: event.key, size: event.size }),
}).pipe(Effect.orDie),
),
),
);
return {
fetch: Effect.succeed(HttpServerResponse.text("ok")),
};
}).pipe(
Effect.provide(
Layer.mergeAll(Lambda.BucketEventSource, AWS.S3.PutObjectHttp),
),
),
);

BucketEventSource: Reading the Event’s Object Version

Section titled “BucketEventSource: Reading the Event’s Object Version”
const getObject = yield* AWS.S3.GetObject(bucket);
yield* AWS.S3.consumeBucketEvents(
bucket,
{ events: ["s3:ObjectCreated:*"], prefix: "incoming/" },
(events) => events.pipe(
Stream.runForEach((event) =>
getObject({ Key: event.key, VersionId: event.versionId }).pipe(
Effect.flatMap(({ Body }) => Stream.runDrain(Body!)),
Effect.orDie,
),
),
),
);
// Provide Lambda.BucketEventSource and AWS.S3.GetObjectHttp on the function.

Passing event.versionId reads the triggering data version even if the key has since been overwritten. Unversioned events omit it and read the current object. Removal events can identify a delete marker or an already-deleted version; they may omit size and eTag and are not object-read signals. sequencer, when present, orders events only for the same object key.

Source: src/AWS/S3/CompleteMultipartUpload.ts

Runtime binding for s3:CompleteMultipartUpload.

Assembles the parts uploaded with UploadPart into the final object. The part list must be in ascending PartNumber order with the ETag each UploadPart call returned. Provide the implementation with Effect.provide(AWS.S3.CompleteMultipartUploadHttp).

CompleteMultipartUpload: Multipart Uploads

Section titled “CompleteMultipartUpload: Multipart Uploads”
// init — bind the operation to the bucket
const completeUpload = yield* AWS.S3.CompleteMultipartUpload(bucket);
// runtime — parts collected from each AWS.S3.UploadPart call
yield* completeUpload({
Key: "backups/archive.tar",
UploadId,
MultipartUpload: {
Parts: [
{ ETag: part1.ETag, PartNumber: 1 },
{ ETag: part2.ETag, PartNumber: 2 },
],
},
});

Source: src/AWS/S3/CopyObject.ts

Runtime binding for s3:CopyObject.

Bind this operation to the destination bucket to get a callable that copies objects server-side — no download/re-upload round trip. CopySource names the source as "source-bucket/key". Provide the implementation with Effect.provide(AWS.S3.CopyObjectHttp). The binding grants reads of current and specific source versions within the bound bucket. Cross-bucket copies require a separate read grant on the source bucket.

// init — bind the operation to the destination bucket
const copyObject = yield* AWS.S3.CopyObject(bucket);
// runtime — promote a staged upload to its final key
yield* copyObject({
CopySource: `${bucketName}/incoming/report.pdf`,
Key: "published/report.pdf",
});
const sourceKey = "reports/annual report.pdf";
const encodedKey = sourceKey.split("/").map(encodeURIComponent).join("/");
yield* copyObject({
CopySource: `${bucketName}/${encodedKey}?versionId=${encodeURIComponent(versionId)}`,
Key: "restored/annual report.pdf",
});

The source version remains unchanged. In a versioned destination bucket, the copy creates a new version and returns its VersionId.

yield* AWS.S3.GetObject(sourceBucket);
const copyObject = yield* AWS.S3.CopyObject(destinationBucket);
// Provide both AWS.S3.GetObjectHttp and AWS.S3.CopyObjectHttp on the host.

The source read binding grants current and version-specific reads without granting writes to the source. Copying tags, ACLs, Object Lock settings, or KMS-encrypted data requires the corresponding additional permissions.

Source: src/AWS/S3/CreateMultipartUpload.ts

Runtime binding for s3:CreateMultipartUpload.

Starts a multipart upload and returns the UploadId that UploadPart, CompleteMultipartUpload, and AbortMultipartUpload reference. Use it for objects too large for a single PutObject (parts are 5 MiB–5 GiB, uploaded independently and in parallel). Provide the implementation with Effect.provide(AWS.S3.CreateMultipartUploadHttp).

// init — bind the operation to the bucket
const createUpload = yield* AWS.S3.CreateMultipartUpload(bucket);
// runtime — object-level metadata (ContentType, etc.) is set here,
// not on the individual parts
const { UploadId } = yield* createUpload({
Key: "backups/archive.tar",
ContentType: "application/x-tar",
});
// pass UploadId to AWS.S3.UploadPart / CompleteMultipartUpload

Source: src/AWS/S3/DeleteObject.ts

Runtime binding for s3:DeleteObject.

Bind this operation to a bucket to get a callable that deletes objects — the bucket name is injected automatically and s3:DeleteObject is granted on the bucket. Provide the implementation with Effect.provide(AWS.S3.DeleteObjectHttp).

// init — bind the operation to the bucket
const deleteObject = yield* AWS.S3.DeleteObject(bucket);
// runtime — deleting a non-existent key succeeds (S3 delete is idempotent)
yield* deleteObject({ Key: "jobs/job-123.json" });

Source: src/AWS/S3/DeleteObjects.ts

Runtime binding for s3:DeleteObjects (batch delete).

Bind this operation to a bucket to get a callable that deletes up to 1,000 objects in a single request — the bucket name is injected automatically and s3:DeleteObject/s3:DeleteObjectVersion are granted on the bucket’s objects. Provide the implementation with Effect.provide(AWS.S3.DeleteObjectsHttp).

// init — bind the operation to the bucket
const deleteObjects = yield* AWS.S3.DeleteObjects(bucket);
// runtime — per-key failures are reported in `Errors`, not thrown
const result = yield* deleteObjects({
Delete: {
Objects: [{ Key: "a.txt" }, { Key: "b.txt" }],
Quiet: true,
},
});

Source: src/AWS/S3/DeleteObjectTagging.ts

Runtime binding for s3:DeleteObjectTagging.

Bind this operation to a bucket to get a callable that removes an object’s entire tag set — the bucket name is injected automatically and s3:DeleteObjectTagging/s3:DeleteObjectVersionTagging are granted on the bucket’s objects. Provide the implementation with Effect.provide(AWS.S3.DeleteObjectTaggingHttp).

const deleteObjectTagging = yield* AWS.S3.DeleteObjectTagging(bucket);
yield* deleteObjectTagging({ Key: "reports/q3.csv" });

Source: src/AWS/S3/GetObject.ts

Runtime binding for s3:GetObject.

Bind this operation to a bucket in the function’s init phase to get a callable that reads objects — the bucket name is injected automatically and s3:GetObject is granted on the bucket. Provide the implementation with Effect.provide(AWS.S3.GetObjectHttp).

Read an Object and Decode Its Body

// init — bind the operation to the bucket
const getObject = yield* AWS.S3.GetObject(bucket);
// runtime — the Body is a Stream; decode it to a string
const text = yield* getObject({ Key: "jobs/job-123.json" }).pipe(
Effect.flatMap((result) =>
Stream.mkString(Stream.decodeText(result.Body!)),
),
);

Treat a Missing Key as Absence

const job = yield* getObject({ Key: `jobs/${jobId}.json` }).pipe(
Effect.catchTag("NoSuchKey", () => Effect.succeed(undefined)),
);

Source: src/AWS/S3/GetObjectAttributes.ts

Runtime binding for s3:GetObjectAttributes.

Bind this operation to a bucket to get a callable that reads object metadata (size, ETag, checksum, storage class, object parts) without fetching the body — the bucket name is injected automatically and the object-read attribute actions are granted on the bucket’s objects. Provide the implementation with Effect.provide(AWS.S3.GetObjectAttributesHttp).

const getObjectAttributes = yield* AWS.S3.GetObjectAttributes(bucket);
const attrs = yield* getObjectAttributes({
Key: "reports/q3.csv",
ObjectAttributes: ["ObjectSize", "ETag", "StorageClass"],
});

Source: src/AWS/S3/GetObjectLegalHold.ts

Runtime binding for s3:GetObjectLegalHold.

Bind this operation to a bucket to get a callable that reads an object’s legal-hold status — the bucket name is injected automatically and s3:GetObjectLegalHold is granted on the bucket’s objects. Requires a bucket created with objectLockEnabled: true. Provide the implementation with Effect.provide(AWS.S3.GetObjectLegalHoldHttp).

const getObjectLegalHold = yield* AWS.S3.GetObjectLegalHold(bucket);
const { LegalHold } = yield* getObjectLegalHold({ Key: "records/1.json" });

Source: src/AWS/S3/GetObjectRetention.ts

Runtime binding for s3:GetObjectRetention.

Bind this operation to a bucket to get a callable that reads an object’s Object Lock retention settings — the bucket name is injected automatically and s3:GetObjectRetention is granted on the bucket’s objects. Requires a bucket created with objectLockEnabled: true. Provide the implementation with Effect.provide(AWS.S3.GetObjectRetentionHttp).

const getObjectRetention = yield* AWS.S3.GetObjectRetention(bucket);
const { Retention } = yield* getObjectRetention({ Key: "records/1.json" });

Source: src/AWS/S3/GetObjectTagging.ts

Runtime binding for s3:GetObjectTagging.

Bind this operation to a bucket to get a callable that reads an object’s tag set — the bucket name is injected automatically and s3:GetObjectTagging/s3:GetObjectVersionTagging are granted on the bucket’s objects. Provide the implementation with Effect.provide(AWS.S3.GetObjectTaggingHttp).

const getObjectTagging = yield* AWS.S3.GetObjectTagging(bucket);
const { TagSet } = yield* getObjectTagging({ Key: "reports/q3.csv" });

Source: src/AWS/S3/HeadObject.ts

Runtime binding for s3:HeadObject.

Bind this operation to a bucket to get a callable that reads an object’s metadata (size, content type, ETag) without downloading the body. Provide the implementation with Effect.provide(AWS.S3.HeadObjectHttp).

The HTTP implementation grants s3:GetObject for current-object reads and s3:GetObjectVersion because this request also supports VersionId. It additionally grants s3:ListBucket so a missing key produces AWS’s documented 404/403 distinction instead of always being reported as AccessDenied. See the HeadObject API and S3 required permissions.

// init — bind the operation to the bucket
const headObject = yield* AWS.S3.HeadObject(bucket);
// runtime — inspect without transferring the body
const head = yield* headObject({ Key: "uploads/report.pdf" });
const size = head.ContentLength;
const contentType = head.ContentType;

Source: src/AWS/S3/ListMultipartUploads.ts

Runtime binding for s3:ListMultipartUploads (s3:ListBucketMultipartUploads).

Bind this operation to a bucket to get a callable that lists in-progress multipart uploads — the bucket name is injected automatically and s3:ListBucketMultipartUploads is granted on the bucket. Provide the implementation with Effect.provide(AWS.S3.ListMultipartUploadsHttp).

const listMultipartUploads = yield* AWS.S3.ListMultipartUploads(bucket);
const result = yield* listMultipartUploads({ Prefix: "uploads/" });
const uploads = result.Uploads ?? [];

Source: src/AWS/S3/ListObjectsV2.ts

Runtime binding for s3:ListObjectsV2.

Bind this operation to a bucket to get a callable that lists objects — the bucket name is injected automatically and s3:ListBucket is granted on the bucket. Provide the implementation with Effect.provide(AWS.S3.ListObjectsV2Http).

// init — bind the operation to the bucket
const listObjects = yield* AWS.S3.ListObjectsV2(bucket);
// runtime — list up to 100 keys under `jobs/`
const result = yield* listObjects({ Prefix: "jobs/", MaxKeys: 100 });
const keys = (result.Contents ?? []).map((object) => object.Key);
// result.IsTruncated + result.NextContinuationToken page through the rest

Source: src/AWS/S3/ListObjectVersions.ts

Runtime binding for s3:ListBucketVersions.

Bind this operation to a bucket to get a callable that lists object versions and delete markers — the bucket name is injected automatically and s3:ListBucketVersions is granted on the bucket. Provide the implementation with Effect.provide(AWS.S3.ListObjectVersionsHttp).

const listObjectVersions = yield* AWS.S3.ListObjectVersions(bucket);
const result = yield* listObjectVersions({ Prefix: "reports/" });
const versions = result.Versions ?? [];

Source: src/AWS/S3/ListParts.ts

Runtime binding for s3:ListParts (s3:ListMultipartUploadParts).

Bind this operation to a bucket to get a callable that lists the parts uploaded for an in-progress multipart upload — the bucket name is injected automatically and s3:ListMultipartUploadParts is granted on the bucket’s objects. Provide the implementation with Effect.provide(AWS.S3.ListPartsHttp).

const listParts = yield* AWS.S3.ListParts(bucket);
const result = yield* listParts({ Key: "large.bin", UploadId: uploadId });
const parts = result.Parts ?? [];

Source: src/AWS/S3/PresignGetObject.ts

Mint presigned download (GET) URLs for objects in a Bucket.

Presigning is a pure SigV4 computation performed client-side with the Function’s own credentials — no S3 API call is made. Because the URL inherits the signer’s IAM permissions, the binding grants s3:GetObject and s3:GetObjectVersion on the bucket’s objects to the host Function. See the S3 presigned URL guide.

PresignGetObject: Presigning Download URLs

Section titled “PresignGetObject: Presigning Download URLs”
const presignGetObject = yield* S3.PresignGetObject(bucket);
const url = yield* presignGetObject({ key: "reports/2026.pdf" });
// hand `url` to a browser — it can download the object without AWS credentials

PresignGetObject: Downloading a Specific Version

Section titled “PresignGetObject: Downloading a Specific Version”
const presignGetObject = yield* S3.PresignGetObject(bucket);
const url = yield* presignGetObject({
key: "reports/2026.pdf",
versionId: "3HL4kqtJlcpXroDTDmJ+rmSpXd3dIbrHY+MTRCxf3vjVBH40Nrjfkd",
});
// downloads this version even if the key has since been overwritten

PresignGetObject: Customizing Download URLs

Section titled “PresignGetObject: Customizing Download URLs”
const url = yield* presignGetObject({
key: "reports/2026.pdf",
expiresIn: 3600, // valid for 1 hour
contentType: "application/pdf",
});

Source: src/AWS/S3/PresignPutObject.ts

Mint presigned upload (PUT) URLs for objects in a Bucket.

Presigning is a pure SigV4 computation performed client-side with the Function’s own credentials — no S3 API call is made. Because the URL inherits the signer’s IAM permissions, the binding grants s3:PutObject on the bucket’s objects to the host Function.

Mint a presigned PUT URL

const presignPutObject = yield* S3.PresignPutObject(bucket);
const url = yield* presignPutObject({ key: "uploads/avatar.png" });
// hand `url` to a browser — it can PUT the object without AWS credentials

Pin the uploaded Content-Type

const url = yield* presignPutObject({
key: "uploads/avatar.png",
expiresIn: 300, // valid for 5 minutes
contentType: "image/png", // uploader must send Content-Type: image/png
});

Source: src/AWS/S3/PutObject.ts

Runtime binding for s3:PutObject.

Bind this operation to a bucket to get a callable that writes objects without manually supplying the bucket name on every request. s3:PutObject is granted on the bucket automatically. Provide the implementation with Effect.provide(AWS.S3.PutObjectHttp).

const putObject = yield* PutObject(bucket);
yield* putObject({
Key: "hello.txt",
Body: "Hello, world!",
ContentType: "text/plain",
});

Source: src/AWS/S3/PutObjectLegalHold.ts

Runtime binding for s3:PutObjectLegalHold.

Bind this operation to a bucket to get a callable that applies or removes a legal hold on an object — the bucket name is injected automatically and s3:PutObjectLegalHold is granted on the bucket’s objects. Requires a bucket created with objectLockEnabled: true. Provide the implementation with Effect.provide(AWS.S3.PutObjectLegalHoldHttp).

const putObjectLegalHold = yield* AWS.S3.PutObjectLegalHold(bucket);
yield* putObjectLegalHold({
Key: "records/1.json",
LegalHold: { Status: "ON" },
});
// … later, release it so the object can be deleted
yield* putObjectLegalHold({
Key: "records/1.json",
LegalHold: { Status: "OFF" },
});

Source: src/AWS/S3/PutObjectRetention.ts

Runtime binding for s3:PutObjectRetention.

Bind this operation to a bucket to get a callable that places an Object Lock retention configuration on an object — the bucket name is injected automatically and s3:PutObjectRetention is granted on the bucket’s objects. Requires a bucket created with objectLockEnabled: true; bypassing a GOVERNANCE retention additionally requires s3:BypassGovernanceRetention. Provide the implementation with Effect.provide(AWS.S3.PutObjectRetentionHttp).

const putObjectRetention = yield* AWS.S3.PutObjectRetention(bucket);
yield* putObjectRetention({
Key: "records/1.json",
Retention: {
Mode: "GOVERNANCE",
RetainUntilDate: new Date(Date.now() + 24 * 60 * 60 * 1000),
},
});

Source: src/AWS/S3/PutObjectTagging.ts

Runtime binding for s3:PutObjectTagging.

Bind this operation to a bucket to get a callable that replaces an object’s tag set — the bucket name is injected automatically and s3:PutObjectTagging/s3:PutObjectVersionTagging are granted on the bucket’s objects. Provide the implementation with Effect.provide(AWS.S3.PutObjectTaggingHttp).

const putObjectTagging = yield* AWS.S3.PutObjectTagging(bucket);
yield* putObjectTagging({
Key: "reports/q3.csv",
Tagging: { TagSet: [{ Key: "status", Value: "final" }] },
});

Source: src/AWS/S3/RestoreObject.ts

Runtime binding for s3:RestoreObject.

Bind this operation to a bucket to get a callable that initiates a restore of an archived (Glacier / Deep Archive) object — the bucket name is injected automatically and s3:RestoreObject is granted on the bucket’s objects. Provide the implementation with Effect.provide(AWS.S3.RestoreObjectHttp).

const restoreObject = yield* AWS.S3.RestoreObject(bucket);
yield* restoreObject({
Key: "archive/2024.tar",
RestoreRequest: {
Days: 3,
GlacierJobParameters: { Tier: "Standard" },
},
});

Source: src/AWS/S3/UploadPart.ts

Runtime binding for s3:UploadPart.

Uploads one part of a multipart upload started with CreateMultipartUpload. Keep each part’s returned ETag — the final CompleteMultipartUpload call needs the full { ETag, PartNumber } list. Provide the implementation with Effect.provide(AWS.S3.UploadPartHttp).

// init — bind the operation to the bucket
const uploadPart = yield* AWS.S3.UploadPart(bucket);
// runtime — PartNumber is 1-based; collect the ETag for completion
const part = yield* uploadPart({
Key: "backups/archive.tar",
UploadId,
PartNumber: 1,
Body: chunk, // 5 MiB–5 GiB except the last part
});
parts.push({ ETag: part.ETag, PartNumber: 1 });

Source: src/AWS/S3/UploadPartCopy.ts

Runtime binding for s3:UploadPartCopy.

Bind this operation to a bucket to get a callable that uploads a multipart part by copying from an existing object — the destination bucket name is injected automatically and s3:PutObject, s3:GetObject, and s3:GetObjectVersion are granted on the bucket’s objects. Select a source version by appending ?versionId=<encoded version ID> to the URL-encoded CopySource bucket/key. Copying from a different source bucket additionally requires read access to that bucket (bind GetObject on it). Provide the implementation with Effect.provide(AWS.S3.UploadPartCopyHttp).

const uploadPartCopy = yield* AWS.S3.UploadPartCopy(bucket);
const part = yield* uploadPartCopy({
Key: "combined.bin",
UploadId: uploadId,
PartNumber: 1,
CopySource: `${bucketName}/source.bin`,
});