Agent skill
aws-s3
Manages file storage with AWS S3 using the JavaScript SDK v3. Use when uploading files, generating presigned URLs, managing buckets, or implementing cloud storage in Node.js applications.
Install this agent skill to your Project
npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/devops/aws-s3
SKILL.md
AWS S3 (JavaScript SDK v3)
Object storage with the AWS SDK for JavaScript v3. Upload files, generate presigned URLs, and manage buckets.
Quick Start
npm install @aws-sdk/client-s3 @aws-sdk/s3-request-presigner
Configure Client
import { S3Client } from '@aws-sdk/client-s3';
const s3Client = new S3Client({
region: process.env.AWS_REGION || 'us-east-1',
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
},
});
Upload Files
Basic Upload
import { PutObjectCommand } from '@aws-sdk/client-s3';
async function uploadFile(bucket, key, body, contentType) {
const command = new PutObjectCommand({
Bucket: bucket,
Key: key,
Body: body,
ContentType: contentType,
});
await s3Client.send(command);
return `https://${bucket}.s3.amazonaws.com/${key}`;
}
// Usage
await uploadFile(
'my-bucket',
'uploads/image.jpg',
fileBuffer,
'image/jpeg'
);
With Options
import { PutObjectCommand } from '@aws-sdk/client-s3';
const command = new PutObjectCommand({
Bucket: 'my-bucket',
Key: 'documents/report.pdf',
Body: fileBuffer,
ContentType: 'application/pdf',
// Access control
ACL: 'private', // private, public-read, public-read-write
// Metadata
Metadata: {
'uploaded-by': 'user-123',
'original-name': 'quarterly-report.pdf',
},
// Caching
CacheControl: 'max-age=31536000',
// Server-side encryption
ServerSideEncryption: 'AES256',
// Content disposition
ContentDisposition: 'attachment; filename="report.pdf"',
// Tags
Tagging: 'environment=production&type=report',
});
await s3Client.send(command);
Multipart Upload (Large Files)
import { Upload } from '@aws-sdk/lib-storage';
async function uploadLargeFile(bucket, key, body) {
const upload = new Upload({
client: s3Client,
params: {
Bucket: bucket,
Key: key,
Body: body,
},
queueSize: 4, // Concurrent parts
partSize: 5 * 1024 * 1024, // 5MB parts
leavePartsOnError: false,
});
upload.on('httpUploadProgress', (progress) => {
console.log(`Progress: ${progress.loaded}/${progress.total}`);
});
await upload.done();
}
Download Files
import { GetObjectCommand } from '@aws-sdk/client-s3';
async function downloadFile(bucket, key) {
const command = new GetObjectCommand({
Bucket: bucket,
Key: key,
});
const response = await s3Client.send(command);
// Convert stream to buffer
const chunks = [];
for await (const chunk of response.Body) {
chunks.push(chunk);
}
return Buffer.concat(chunks);
}
// Or get as string
async function getFileAsString(bucket, key) {
const command = new GetObjectCommand({
Bucket: bucket,
Key: key,
});
const response = await s3Client.send(command);
return response.Body.transformToString();
}
Presigned URLs
Generate temporary URLs for upload/download without sharing credentials.
Presigned Download URL
import { GetObjectCommand } from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
async function getDownloadUrl(bucket, key, expiresIn = 3600) {
const command = new GetObjectCommand({
Bucket: bucket,
Key: key,
});
const url = await getSignedUrl(s3Client, command, { expiresIn });
return url;
}
// Usage - valid for 1 hour
const downloadUrl = await getDownloadUrl('my-bucket', 'files/doc.pdf');
Presigned Upload URL
import { PutObjectCommand } from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
async function getUploadUrl(bucket, key, contentType, expiresIn = 3600) {
const command = new PutObjectCommand({
Bucket: bucket,
Key: key,
ContentType: contentType,
});
const url = await getSignedUrl(s3Client, command, { expiresIn });
return url;
}
// Usage
const uploadUrl = await getUploadUrl(
'my-bucket',
'uploads/image.jpg',
'image/jpeg'
);
// Client can PUT to this URL
await fetch(uploadUrl, {
method: 'PUT',
body: file,
headers: {
'Content-Type': 'image/jpeg',
},
});
Presigned POST (Browser Uploads)
import { createPresignedPost } from '@aws-sdk/s3-presigned-post';
async function getUploadForm(bucket, key) {
const { url, fields } = await createPresignedPost(s3Client, {
Bucket: bucket,
Key: key,
Conditions: [
['content-length-range', 0, 10 * 1024 * 1024], // Max 10MB
['starts-with', '$Content-Type', 'image/'],
],
Expires: 3600,
});
return { url, fields };
}
// Client-side usage
const { url, fields } = await getUploadForm('my-bucket', 'uploads/${filename}');
const formData = new FormData();
Object.entries(fields).forEach(([key, value]) => {
formData.append(key, value);
});
formData.append('file', file);
await fetch(url, {
method: 'POST',
body: formData,
});
List Objects
import { ListObjectsV2Command } from '@aws-sdk/client-s3';
async function listFiles(bucket, prefix = '') {
const command = new ListObjectsV2Command({
Bucket: bucket,
Prefix: prefix,
MaxKeys: 100,
});
const response = await s3Client.send(command);
return response.Contents?.map((item) => ({
key: item.Key,
size: item.Size,
lastModified: item.LastModified,
})) || [];
}
// Paginate through all files
async function* listAllFiles(bucket, prefix = '') {
let continuationToken;
do {
const command = new ListObjectsV2Command({
Bucket: bucket,
Prefix: prefix,
ContinuationToken: continuationToken,
});
const response = await s3Client.send(command);
for (const item of response.Contents || []) {
yield item;
}
continuationToken = response.NextContinuationToken;
} while (continuationToken);
}
// Usage
for await (const file of listAllFiles('my-bucket', 'uploads/')) {
console.log(file.Key);
}
Delete Files
import { DeleteObjectCommand, DeleteObjectsCommand } from '@aws-sdk/client-s3';
// Delete single file
async function deleteFile(bucket, key) {
const command = new DeleteObjectCommand({
Bucket: bucket,
Key: key,
});
await s3Client.send(command);
}
// Delete multiple files
async function deleteFiles(bucket, keys) {
const command = new DeleteObjectsCommand({
Bucket: bucket,
Delete: {
Objects: keys.map((Key) => ({ Key })),
},
});
const response = await s3Client.send(command);
return response.Deleted;
}
// Usage
await deleteFiles('my-bucket', [
'uploads/old1.jpg',
'uploads/old2.jpg',
]);
Copy & Move Files
import { CopyObjectCommand, DeleteObjectCommand } from '@aws-sdk/client-s3';
async function copyFile(bucket, sourceKey, destinationKey) {
const command = new CopyObjectCommand({
Bucket: bucket,
CopySource: `${bucket}/${sourceKey}`,
Key: destinationKey,
});
await s3Client.send(command);
}
async function moveFile(bucket, sourceKey, destinationKey) {
await copyFile(bucket, sourceKey, destinationKey);
await deleteFile(bucket, sourceKey);
}
Check If File Exists
import { HeadObjectCommand } from '@aws-sdk/client-s3';
async function fileExists(bucket, key) {
try {
const command = new HeadObjectCommand({
Bucket: bucket,
Key: key,
});
await s3Client.send(command);
return true;
} catch (error) {
if (error.name === 'NotFound') {
return false;
}
throw error;
}
}
Next.js API Routes
// app/api/upload/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { PutObjectCommand } from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
import { s3Client } from '@/lib/s3';
export async function POST(request: NextRequest) {
const { filename, contentType } = await request.json();
const key = `uploads/${Date.now()}-${filename}`;
const command = new PutObjectCommand({
Bucket: process.env.S3_BUCKET!,
Key: key,
ContentType: contentType,
});
const uploadUrl = await getSignedUrl(s3Client, command, { expiresIn: 3600 });
return NextResponse.json({
uploadUrl,
key,
publicUrl: `https://${process.env.S3_BUCKET}.s3.amazonaws.com/${key}`,
});
}
// Client component
async function handleUpload(file: File) {
// Get presigned URL
const response = await fetch('/api/upload', {
method: 'POST',
body: JSON.stringify({
filename: file.name,
contentType: file.type,
}),
});
const { uploadUrl, publicUrl } = await response.json();
// Upload directly to S3
await fetch(uploadUrl, {
method: 'PUT',
body: file,
headers: {
'Content-Type': file.type,
},
});
return publicUrl;
}
CORS Configuration
Set in AWS Console or via SDK:
[
{
"AllowedHeaders": ["*"],
"AllowedMethods": ["GET", "PUT", "POST", "DELETE"],
"AllowedOrigins": ["https://yourdomain.com"],
"ExposeHeaders": ["ETag"]
}
]
With CloudFront
Use CloudFront for CDN delivery:
const cloudFrontUrl = `https://d1234.cloudfront.net/${key}`;
R2 Compatibility (Cloudflare)
S3 SDK works with Cloudflare R2:
const s3Client = new S3Client({
region: 'auto',
endpoint: `https://${accountId}.r2.cloudflarestorage.com`,
credentials: {
accessKeyId: R2_ACCESS_KEY_ID,
secretAccessKey: R2_SECRET_ACCESS_KEY,
},
});
Best Practices
- Use presigned URLs for client uploads (don't expose credentials)
- Set appropriate CORS for browser uploads
- Use multipart upload for files > 100MB
- Enable versioning for important buckets
- Set lifecycle rules to clean up old files
- Use CloudFront for public files (faster, cheaper)
- Encrypt sensitive data with SSE
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
Didn't find tool you were looking for?