Skip to content

S3

Amazon Simple Storage Service for storing and retrieving any amount of data from anywhere.

Protocol: RestXmlSigning name: s3Persistent: Yes — bucket and object metadata persists; object bodies persist when --data-dir is supplied (see Behavior Notes)

Quick Start

Create a bucket, upload a file, and download it:

bash
# Create a bucket
curl -s -X PUT http://localhost:4566/my-bucket \
  -H "Authorization: AWS4-HMAC-SHA256 Credential=test/20260421/us-east-1/s3/aws4_request, SignedHeaders=host, Signature=fake"

# Upload an object
curl -s -X PUT http://localhost:4566/my-bucket/hello.txt \
  -H "Content-Type: text/plain" \
  -H "Authorization: AWS4-HMAC-SHA256 Credential=test/20260421/us-east-1/s3/aws4_request, SignedHeaders=host, Signature=fake" \
  -d "Hello, World!"

# Download the object
curl -s http://localhost:4566/my-bucket/hello.txt \
  -H "Authorization: AWS4-HMAC-SHA256 Credential=test/20260421/us-east-1/s3/aws4_request, SignedHeaders=host, Signature=fake"

# List objects in bucket
curl -s "http://localhost:4566/my-bucket?list-type=2" \
  -H "Authorization: AWS4-HMAC-SHA256 Credential=test/20260421/us-east-1/s3/aws4_request, SignedHeaders=host, Signature=fake"

Configuration

S3 state is global per account — it does not vary by region. All buckets are visible regardless of the region in the request.

Use forcePathStyle: true in your SDK client to use path-style URLs (/bucket/key) instead of virtual-hosted style (bucket.localhost/key).

Operations

Bucket Operations

OperationDescription
ListBucketsList all buckets. Returns Buckets list with Name and CreationDate. No input required
CreateBucketCreate a new bucket. Input: bucket name in URL path, optional CreateBucketConfiguration XML (for non-us-east-1 regions). Returns empty CreateBucketResult
DeleteBucketDelete a bucket. Must be empty first (no objects). Input: bucket name in URL path
HeadBucketCheck if a bucket exists. Returns 200 (exists) or 404 (not found). No body returned
GetBucketLocationGet the bucket's region. Returns LocationConstraint (empty string for us-east-1)
PutBucketVersioningEnable or suspend versioning. Input: VersioningConfiguration XML with Status: Enabled/Suspended
GetBucketVersioningGet versioning configuration
PutBucketTaggingSet bucket tags. Input: Tagging XML with TagSet
GetBucketTaggingGet bucket tags. Returns TagSet
DeleteBucketTaggingRemove all bucket tags
PutBucketPolicySet a bucket policy (JSON). Input: raw JSON policy body
GetBucketPolicyGet bucket policy (raw JSON)
DeleteBucketPolicyRemove bucket policy
PutBucketCorsSet CORS configuration. Input: CORSConfiguration XML
GetBucketCorsGet CORS configuration
DeleteBucketCorsRemove CORS configuration
PutBucketNotificationConfigurationConfigure S3 event notifications (SQS, SNS, Lambda)
GetBucketNotificationConfigurationGet event notification config

ACL Operations

OperationDescription
GetBucketAclGet the access control list for a bucket. Returns Owner and Grants list
PutBucketAclSet the access control list for a bucket. Input: AccessControlPolicy XML or a canned ACL header
GetObjectAclGet the ACL for an object. Returns Owner and Grants list

Lifecycle Operations

OperationDescription
GetBucketLifecycleConfigurationGet lifecycle rules for a bucket. Returns Rules list
PutBucketLifecycleConfigurationSet lifecycle rules. Input: LifecycleConfiguration XML with Rule elements
DeleteBucketLifecycleConfigurationRemove all lifecycle rules from a bucket

Encryption Operations

OperationDescription
GetBucketEncryptionGet the default server-side encryption configuration for a bucket
PutBucketEncryptionSet the default encryption. Input: ServerSideEncryptionConfiguration XML
DeleteBucketEncryptionRemove the default encryption configuration

Logging Operations

OperationDescription
GetBucketLoggingGet the logging configuration for a bucket. Returns LoggingEnabled element if configured
PutBucketLoggingSet the logging configuration. Input: BucketLoggingStatus XML

Object Operations

OperationDescription
PutObjectUpload an object. Input: PUT /{bucket}/{key}, body is object data, Content-Type header sets MIME type. Returns: ETag header, plus x-amz-version-id when the bucket has versioning enabled
GetObjectDownload an object. Input: GET /{bucket}/{key}, optional Range header, optional ?versionId=X to fetch a historical version. Reads landing on a delete marker return 404 with x-amz-delete-marker: true
HeadObjectGet object metadata without the body. Same ?versionId=X semantics as GetObject
DeleteObjectDelete an object. Input: DELETE /{bucket}/{key} (optional ?versionId=X for permanent per-version delete). On versioning-enabled buckets without VersionId, pushes a delete marker; with VersionId, permanently removes that single entry
CopyObjectCopy an object within or between buckets. Input: PUT /{dest-bucket}/{dest-key} with x-amz-copy-source: /{src-bucket}/{src-key}[?versionId=X] header. Returns the new object's VersionId in x-amz-version-id when the destination bucket has versioning enabled
ListObjectsV2List objects with optional prefix and delimiter. Skips keys whose latest version is a delete marker. Input: GET /{bucket}?list-type=2&prefix=...&delimiter=/&max-keys=1000
ListObjectVersionsList every version + delete marker for a bucket, newest first per key with IsLatest set on the most recent. Input: GET /{bucket}?versions
DeleteObjectsBatch delete objects. Input: POST /{bucket}?delete with Delete XML listing keys (and optional VersionId per entry). Honors versioning: writes delete markers on Enabled/Suspended buckets, permanently removes a specific version when VersionId is supplied
PutObjectTaggingSet object tags. Input: PUT /{bucket}/{key}?tagging with Tagging XML
GetObjectTaggingGet object tags
DeleteObjectTaggingRemove object tags

Multipart Upload

OperationDescription
CreateMultipartUploadInitiate a multipart upload. Returns UploadId
UploadPartUpload a part. Input: PUT /{bucket}/{key}?partNumber={N}&uploadId={id}, body is part data. Returns ETag
CompleteMultipartUploadComplete the upload. Input: POST /{bucket}/{key}?uploadId={id} with CompleteMultipartUpload XML listing all parts and their ETags. Returns the new object's VersionId when the bucket has versioning enabled
UploadPartCopyCopy bytes from a source object directly into a multipart part. Distinguished from UploadPart by an x-amz-copy-source header on the same PUT /{bucket}/{key}?partNumber=&uploadId= request, with optional x-amz-copy-source-range: bytes=start-end
AbortMultipartUploadCancel an in-progress upload. Input: DELETE /{bucket}/{key}?uploadId={id}
ListMultipartUploadsList in-progress uploads for a bucket
ListPartsList uploaded parts for a multipart upload

SDK Example

typescript
import {
  S3Client,
  CreateBucketCommand,
  PutObjectCommand,
  GetObjectCommand,
  ListObjectsV2Command,
  DeleteObjectCommand,
  CopyObjectCommand,
} from '@aws-sdk/client-s3';
import { Readable } from 'stream';

const s3 = new S3Client({
  region: 'us-east-1',
  endpoint: 'http://localhost:4566',
  credentials: { accessKeyId: 'test', secretAccessKey: 'test' },
  forcePathStyle: true, // Required for AWSim path-style URLs
});

// Create bucket
await s3.send(new CreateBucketCommand({ Bucket: 'my-bucket' }));

// Upload text
await s3.send(new PutObjectCommand({
  Bucket: 'my-bucket',
  Key: 'config/settings.json',
  Body: JSON.stringify({ debug: true, version: '1.0.0' }),
  ContentType: 'application/json',
}));

// Upload binary
import { readFileSync } from 'fs';
await s3.send(new PutObjectCommand({
  Bucket: 'my-bucket',
  Key: 'images/logo.png',
  Body: readFileSync('logo.png'),
  ContentType: 'image/png',
}));

// Download and read
const response = await s3.send(new GetObjectCommand({
  Bucket: 'my-bucket',
  Key: 'config/settings.json',
}));
const text = await response.Body?.transformToString();
console.log(JSON.parse(text!));

// List objects with prefix
const { Contents, CommonPrefixes } = await s3.send(new ListObjectsV2Command({
  Bucket: 'my-bucket',
  Prefix: 'config/',
  Delimiter: '/',
}));
console.log('Objects:', Contents?.map(o => o.Key));

// Copy object
await s3.send(new CopyObjectCommand({
  Bucket: 'my-bucket',
  Key: 'config/settings-backup.json',
  CopySource: '/my-bucket/config/settings.json',
}));

// Delete object
await s3.send(new DeleteObjectCommand({
  Bucket: 'my-bucket',
  Key: 'config/settings-backup.json',
}));

CLI Example

bash
# Bucket operations
aws --endpoint-url http://localhost:4566 s3 mb s3://my-bucket
aws --endpoint-url http://localhost:4566 s3 ls

# Object operations
aws --endpoint-url http://localhost:4566 s3 cp file.txt s3://my-bucket/file.txt
aws --endpoint-url http://localhost:4566 s3 cp s3://my-bucket/file.txt ./downloaded.txt
aws --endpoint-url http://localhost:4566 s3 ls s3://my-bucket/
aws --endpoint-url http://localhost:4566 s3 rm s3://my-bucket/file.txt

# Sync a directory
aws --endpoint-url http://localhost:4566 s3 sync ./dist s3://my-bucket/dist/

# Low-level API access
aws --endpoint-url http://localhost:4566 s3api put-object \
  --bucket my-bucket \
  --key data/record.json \
  --body record.json \
  --content-type application/json

aws --endpoint-url http://localhost:4566 s3api list-objects-v2 \
  --bucket my-bucket \
  --prefix data/ \
  --max-keys 100

Event Notifications

S3 can notify SQS queues, SNS topics, or Lambda functions when objects are created or deleted. See Cross-Service Integrations.

ACL Example

bash
# Get bucket ACL
aws --endpoint-url http://localhost:4566 s3api get-bucket-acl --bucket my-bucket

# Get object ACL
aws --endpoint-url http://localhost:4566 s3api get-object-acl \
  --bucket my-bucket \
  --key my-object.txt

Lifecycle Example

bash
# Put lifecycle configuration
aws --endpoint-url http://localhost:4566 s3api put-bucket-lifecycle-configuration \
  --bucket my-bucket \
  --lifecycle-configuration '{"Rules":[{"ID":"expire-old","Status":"Enabled","Expiration":{"Days":30},"Filter":{"Prefix":"tmp/"}}]}'

# Get lifecycle configuration
aws --endpoint-url http://localhost:4566 s3api get-bucket-lifecycle-configuration \
  --bucket my-bucket

Encryption Example

bash
# Enable AES-256 default encryption
aws --endpoint-url http://localhost:4566 s3api put-bucket-encryption \
  --bucket my-bucket \
  --server-side-encryption-configuration \
  '{"Rules":[{"ApplyServerSideEncryptionByDefault":{"SSEAlgorithm":"AES256"}}]}'

# Get encryption configuration
aws --endpoint-url http://localhost:4566 s3api get-bucket-encryption --bucket my-bucket

Behavior Notes

  • Object metadata (key, size, ETag, content-type) is always persisted across restarts via the regular snapshot. Object bodies are persisted to disk under {data_dir}/s3/objects/<bucket>/<key> only when --data-dir is supplied; without it, bodies stay in memory and are lost on restart. Multipart parts under an in-progress upload are persisted under {data_dir}/s3/multipart/<bucket>/<upload-id>/. See Persistence.
  • Presigned URLs are not supported.
  • Bucket ACLs and object ACLs are accepted and stored but not enforced — all objects are accessible to any caller in AWSim.
  • Lifecycle rules are accepted and stored but items are not automatically expired or transitioned.
  • Encryption configuration is accepted and stored but objects are not actually encrypted at rest in AWSim.
  • Logging configuration is accepted and stored but no log delivery occurs in AWSim.
  • Object versioning is fully modelled: enabling it on a bucket retains every prior version, GetObject/HeadObject accept ?versionId= for historical reads, DeleteObject without VersionId writes a delete marker (and with a VersionId permanently removes that single entry), and ListObjectVersions enumerates everything including delete markers. Each version's body is persisted under its own blob key ({key}@v={version_id_or_null}), so historical reads survive snapshot+restore.
  • ListObjectsV2 supports Prefix filtering, Delimiter for virtual folder grouping (CommonPrefixes), start-after parameter, and pagination via continuation-token. The KeyCount response field includes both Contents and CommonPrefixes entries.
  • The forcePathStyle: true SDK option is required — virtual-hosted style (bucket.localhost:4566) is not recommended for local development.
  • Multipart ETags follow AWS format: the ETag is the MD5 digest of the concatenated per-part MD5 binary digests, followed by -N where N is the part count (e.g., "a54357aff...-3"). Single-part uploads use a simple MD5 ETag.
  • CompleteMultipartUpload only concatenates parts listed in the request body (with ETag validation), matching AWS behavior. Part numbers must be 1–10000.
  • DeleteObjects returns per-key error entries (with Code and Message) for any keys that fail to delete, matching AWS behavior.
  • HeadObject and GetObject return user metadata headers (x-amz-meta-*) that were supplied during PutObject or CreateMultipartUpload.
  • GetObject returns Accept-Ranges: bytes and HTTP 206 Partial Content for range requests with Content-Range header.
  • PutObject persists Content-Encoding, Cache-Control, Content-Disposition, Content-Language, and Expires headers; they are returned on subsequent GET/HEAD.
  • CopyObject returns CopySourceVersionId when the source object has a version ID.
  • Bucket/Object ACLs use the caller's actual account ID as the canonical owner ID.
  • GetBucketEncryption returns ServerSideEncryptionConfigurationNotFoundError when no encryption has been explicitly set (matching AWS).
  • Bucket name validation rejects IP-address format, xn-- prefix, -s3alias/--ol-s3/--x-s3 suffixes, and consecutive dots.
  • Delete markers are properly surfaced via x-amz-delete-marker: true and x-amz-version-id HTTP headers on 404 responses.

Released under MIT / Apache-2.0 License