Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Handle S3 file uploads including UI components, client-side logic, and server-side processing
.claude/skills/aiskillstore-s3-upload-handler/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | -61% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 12% | 0% |
| case-04 | ✗→✓ | ▲ Improved | -14% | 0% |
| case-05 | ✗→✓ | ▲ Improved | -44% | 0% |
| case-07 | ✗→✓ | ▲ Improved | -6% | 0% |
This skill provides methods to handle file uploads to AWS S3 using pre-built UI components, custom client-side logic, or server-side processing.
S3Uploader for drag-and-drop or button uploads.ClientS3Uploader class for custom upload interfaces.uploadFromServer for backend file processing and uploading.Use the S3Uploader component for most user-facing upload needs.
tsximport { S3Uploader } from "@/components/ui/s3-uploader/s3-uploader"; // Usage in a form or page <S3Uploader presignedRouteProvider="/api/app/your-upload-route" // API route to get signed URL variant="dropzone" // or "button" maxFiles={5} accept="image/*" // or specific extensions like ".pdf,.doc" onUpload={async (fileUrls) => { console.log("Files uploaded:", fileUrls); // Handle success (e.g., update form state) }} />
Use ClientS3Uploader when you need full control over the UI (e.g., hidden inputs, custom buttons).
tsximport { ClientS3Uploader } from "@/lib/s3/clientS3Uploader"; // Initialize const uploader = new ClientS3Uploader({ presignedRouteProvider: "/api/app/your-upload-route" }); // Upload a file const url = await uploader.uploadFile(fileObject);
Use uploadFromServer in API routes or Server Actions for processing files (resizing, generating PDFs) before storage.
typescriptimport uploadFromServer from "@/lib/s3/uploadFromServer"; // In a server context (API route/Action) const url = await uploadFromServer({ file: base64String, // File content as base64 path: "uploads/users/avatar.jpg", // Destination path in bucket contentType: "image/jpeg" // MIME type });
Ensure .env.local has the necessary AWS credentials:
envAWS_ACCESS_KEY_ID="your-access-key" AWS_SECRET_ACCESS_KEY="your-secret-key" AWS_REGION="us-east-1" AWS_S3_BUCKET_NAME="your-bucket-name"
You typically need an API route to generate presigned URLs for the client uploader.
typescript// src/app/api/app/upload-input-images/route.ts import { createPresignedPost } from "@aws-sdk/s3-presigned-post"; import { S3Client } from "@aws-sdk/client-s3"; import { v4 as uuidv4 } from "uuid"; export async function POST(request: Request) { const { fileName, fileType } = await request.json(); const client = new S3Client({ region: process.env.AWS_REGION }); const { url, fields } = await createPresignedPost(client, { Bucket: process.env.AWS_S3_BUCKET_NAME!, Key: `uploads/${uuidv4()}-${fileName}`, Conditions: [ ["content-length-range", 0, 10485760], // up to 10 MB ["starts-with", "$Content-Type", fileType], ], Fields: { acl: "public-read", "Content-Type": fileType, }, Expires: 600, // Seconds before the presigned post expires. 3600 by default. }); return Response.json({ url, fields }); }
Refer to reference.md for more details.
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→pass | 21,835 | 4,174 | -81% | 1 | 1 | 0% | 4,626 | 1,796 | -61% | 0 | 0 | — |
case-02 | fail→pass | 12,757 | 9,304 | -27% | 1 | 1 | 0% | 2,597 | 2,917 | +12% | 0 | 0 | — |
case-03 | fail→fail | 11,402 | 8,408 | -26% | 1 | 1 | 0% | 2,375 | 2,811 | +18% | 0 | 0 | — |
case-04 | fail→pass | 10,247 | 4,289 | -58% | 1 | 1 | 0% | 2,156 | 1,851 | -14% | 0 | 0 | — |
case-05 | fail→pass | 17,440 | 4,590 | -74% | 1 | 1 | 0% | 3,268 | 1,836 | -44% | 0 | 0 | — |
case-06 | pass→pass | 12,091 | 2,549 | -79% | 1 | 1 | 0% | 1,988 | 1,357 | -32% | 0 | 0 | — |
case-07 | fail→pass | 8,325 | 2,730 | -67% | 1 | 1 | 0% | 1,565 | 1,466 | -6% | 0 | 0 | — |
case-08 | pass→pass | 10,561 | 3,214 | -70% | 1 | 1 | 0% | 1,859 | 1,537 | -17% | 0 | 0 | — |
case-09 | pass→pass | 15,312 | 8,321 | -46% | 1 | 1 | 0% | 2,408 | 2,314 | -4% | 0 | 0 | — |
case-10 | pass→pass | 10,368 | 2,748 | -73% | 1 | 1 | 0% | 1,850 | 1,438 | -22% | 0 | 0 | — |
case-11 | pass→pass | 11,672 | 4,328 | -63% | 1 | 1 | 0% | 2,014 | 1,767 | -12% | 0 | 0 | — |
case-12 | fail→pass | 12,468 | 3,348 | -73% | 1 | 1 | 0% | 2,043 | 1,568 | -23% | 0 | 0 | — |
case-13 | fail→pass | 8,479 | 3,068 | -64% | 1 | 1 | 0% | 1,522 | 1,513 | -1% | 0 | 0 | — |
case-14 | fail→fail | 17,076 | 6,579 | -61% | 1 | 1 | 0% | 3,206 | 2,153 | -33% | 0 | 0 | — |
case-15 | pass→fail | 3,393 | 1,571 | -54% | 1 | 1 | 0% | 592 | 1,203 | +103% | 0 | 0 | — |
case-16 | pass→pass | 12,252 | 4,306 | -65% | 1 | 1 | 0% | 2,169 | 1,745 | -20% | 0 | 0 | — |
case-17 | pass→pass | 11,297 | 3,918 | -65% | 1 | 1 | 0% | 2,134 | 1,693 | -21% | 0 | 0 | — |
case-18 | fail→pass | 4,221 | 2,181 | -48% | 1 | 1 | 0% | 749 | 1,303 | +74% | 0 | 0 | — |
case-19 | fail→pass | 10,400 | 2,594 | -75% | 1 | 1 | 0% | 1,749 | 1,363 | -22% | 0 | 0 | — |
case-20 | pass→pass | 5,713 | 3,670 | -36% | 1 | 1 | 0% | 1,052 | 1,602 | +52% | 0 | 0 | — |
case-21 | pass→fail | 14,444 | 9,056 | -37% | 1 | 1 | 0% | 2,858 | 2,717 | -5% | 0 | 0 | — |
case-22 | pass→pass | 8,196 | 7,949 | -3% | 1 | 1 | 0% | 1,615 | 2,639 | +63% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +32 percentage points is the difference between those two pass rates over the 22 comparable cases. 2 cases got worse with the skill loaded, and they are included in that figure.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.