Claude Code Plugins

Community-maintained marketplace

Feedback

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 Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name aws-s3
description 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.

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

  1. Use presigned URLs for client uploads (don't expose credentials)
  2. Set appropriate CORS for browser uploads
  3. Use multipart upload for files > 100MB
  4. Enable versioning for important buckets
  5. Set lifecycle rules to clean up old files
  6. Use CloudFront for public files (faster, cheaper)
  7. Encrypt sensitive data with SSE