Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

Amazon S3 File Uploads with the AWS SDK

By Kokil Thapa | Last reviewed: September 2026

Amazon S3 file uploads with the AWS SDK are the standard way to move user files, exports, and media off your web server and into durable object storage. Whether you run Laravel on EC2, a Node.js API, or a Python worker, the SDK wraps S3's REST API with retries, signing, and streaming so you do not hand-roll SigV4 requests. This guide walks through install, configuration, single and multipart uploads, presigned URLs, and the production mistakes I see on real client projects—including patterns from Laravel S3 file storage setup and document portals that rely on secure uploads.

What is the AWS SDK and how does it handle Amazon S3 file uploads?

The AWS SDK is Amazon's official client library for each language runtime. It signs HTTP requests, handles retries with exponential backoff, and exposes high-level methods like putObject and upload. You never send raw REST calls to s3.amazonaws.com unless you have a very specific reason.

S3 stores objects in buckets. Each object has a key (path), optional metadata, ACL or bucket-policy permissions, and optional server-side encryption. The SDK serialises your file bytes—or a stream—into that object model.

On production Laravel applications I maintain, the SDK sits between the app and S3 like this:

S3 Upload ArchitectureWeb AppLaravel / Node / PythonAWS SDKSign + retry + streamAmazon S3Bucket + object keyOptional: CloudFront CDN, KMS encryption, lifecycle rulesIAM role on EC2 avoids long-lived access keys
Amazon S3 file uploads with the AWS SDK: application, signed client, and bucket

The SDK supports three credential sources in order of preference for servers:

  • IAM instance profile on EC2 or ECS task role—no keys in .env
  • Environment variables AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY
  • Shared credentials file at ~/.aws/credentials for local dev

For local development against S3-compatible storage, see MinIO self-hosted S3 storage. The same SDK calls work when you set a custom endpoint.

How do you install and configure the AWS SDK for S3 uploads?

Pick the SDK package for your runtime. All share the same concepts: create a client, pass region and credentials, call an upload operation.

PHP (Laravel and standalone)

Laravel 12 and 13 ship with Flysystem S3 support, which uses aws/aws-sdk-php under the hood. For direct SDK usage or custom logic outside Flysystem:

composer require aws/aws-sdk-php:^3.0

# .env — never commit real keys
AWS_ACCESS_KEY_ID=AKIA...
AWS_SECRET_ACCESS_KEY=...
AWS_DEFAULT_REGION=ap-south-1
AWS_BUCKET=my-app-uploads
<?php
use Aws\S3\S3Client;

$s3 = new S3Client([
    'version' => 'latest',
    'region'  => env('AWS_DEFAULT_REGION', 'ap-south-1'),
    'credentials' => [
        'key'    => env('AWS_ACCESS_KEY_ID'),
        'secret' => env('AWS_SECRET_ACCESS_KEY'),
    ],
]);

PHP 8.3 or higher is required for Laravel 13. PHP 8.2 works with Laravel 12. Store secrets in AWS Secrets Manager on production rather than plain environment files when compliance matters.

Node.js

npm install @aws-sdk/client-s3 @aws-sdk/lib-storage

# package.json should target Node.js 26 LTS or 24 LTS

Python

pip install boto3

# boto3 picks up ~/.aws/credentials or env vars automatically

Region choice matters. Nepal-based apps often use ap-south-1 (Mumbai) for lower latency and predictable data residency within AWS's published region list. Cross-check pricing against Cloudflare R2 vs AWS S3 cost breakdown if egress fees dominate your bill.

SDK packageRuntimeBest forUpload helper
aws/aws-sdk-phpPHP 8.2+Laravel, Symfony APIsS3Client::putObject, TransferManager
@aws-sdk/client-s3Node.js 24/26 LTSExpress, serverless handlersPutObjectCommand, @aws-sdk/lib-storage
boto3Python 3.10+Workers, data pipelinesclient.upload_file, multipart config

How do you upload a file to Amazon S3 using PutObject?

PutObject is the simplest upload API. Use it for files under roughly 100 MB. AWS allows up to 5 GB per PutObject, but loading a multi-gigabyte file into memory is a bad idea on a typical web server.

PutObject Upload Flow1. Credentials2. S3 Client3. PutObject4. ETag OKRequired parametersBucket, Key, Body (or SourceFile stream)ContentType, ServerSideEncryption, MetadataACL: prefer bucket policy over public-read ACLValidate file type and size on the server first
Single-request PutObject path for Amazon S3 file uploads with the AWS SDK

PHP example

$result = $s3->putObject([
    'Bucket'      => env('AWS_BUCKET'),
    'Key'         => 'uploads/' . $safeFilename,
    'SourceFile'  => $request->file('document')->getRealPath(),
    'ContentType' => $request->file('document')->getMimeType(),
    'ServerSideEncryption' => 'AES256',
    'Metadata'    => [
        'uploaded-by' => (string) auth()->id(),
    ],
]);

$etag = $result['ETag'];
$url  = $s3->getObjectUrl(env('AWS_BUCKET'), 'uploads/' . $safeFilename);

Node.js example

import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
import { readFileSync } from "fs";

const client = new S3Client({ region: "ap-south-1" });

await client.send(new PutObjectCommand({
  Bucket: "my-app-uploads",
  Key: `uploads/${safeName}`,
  Body: readFileSync(localPath),
  ContentType: "application/pdf",
  ServerSideEncryption: "AES256",
}));

Python example

import boto3

s3 = boto3.client("s3", region_name="ap-south-1")

s3.upload_file(
    localPath,
    "my-app-uploads",
    f"uploads/{safe_name}",
    ExtraArgs={
        "ContentType": "application/pdf",
        "ServerSideEncryption": "AES256",
    },
)

Always sanitise the object key. Strip path traversal, normalise Unicode, and generate your own filename rather than trusting the client's original name. Full rules live in our file upload security guide.

For Laravel-specific Flysystem configuration—disk drivers, visibility, and URL generation—read Laravel file uploads with S3, R2, and local storage. The SDK layer is the same; Laravel wraps it.

When should you use multipart upload instead of PutObject?

Multipart upload splits a large file into parts (minimum 5 MB each, except the last). Parts upload in parallel. A failed part retries without restarting the whole file. The SDK's high-level upload helpers switch to multipart automatically above a threshold.

Upload Method ChoiceFile size?Under 100 MBUse PutObject100 MB to 5 GBMultipart uploadOver 5 GBMultipart requiredStream from disk — never load entire file into RAM on PHP-FPM workersAbort incomplete multipart uploads via lifecycle rule
Choosing PutObject or multipart for Amazon S3 file uploads with the AWS SDK

PHP TransferManager (automatic multipart)

use Aws\S3\Transfer;

$transfer = new Transfer($s3, '/tmp/large-export.zip', 's3://my-app-uploads/exports/large-export.zip');
$transfer->transfer();

Node.js @aws-sdk/lib-storage

import { Upload } from "@aws-sdk/lib-storage";

const upload = new Upload({
  client,
  params: {
    Bucket: "my-app-uploads",
    Key: "exports/large-export.zip",
    Body: createReadStream("/tmp/large-export.zip"),
  },
  queueSize: 4,
  partSize: 10 * 1024 * 1024,
});

await upload.done();

A common production bug is orphaned multipart uploads after client disconnects. Set a bucket lifecycle rule to abort incomplete uploads after seven days. That saves storage cost on half-finished 2 GB video uploads.

For nightly database dumps and media archives, the same SDK patterns appear in automated off-site backups to S3. Treat backups as uploads with encryption and a predictable key prefix like backups/2026/09/09/.

How do you upload directly from the browser with presigned URLs?

Routing every file through your PHP-FPM or Node server wastes bandwidth and ties up workers. Presigned URLs let the client PUT or POST straight to S3 with a time-limited signature your backend generates.

Presigned URL UploadBrowserUser selects fileYour APICreates presigned URLAmazon S3Receives PUTDonesigned URLBackend validates auth before signingSet short expiry (5–15 min) and exact Content-TypeConfirm upload via S3 event or headObject callback
Presigned URLs for direct Amazon S3 file uploads with the AWS SDK

PHP presigned PUT URL

use Aws\S3\S3Client;

$cmd = $s3->getCommand('PutObject', [
    'Bucket'      => env('AWS_BUCKET'),
    'Key'         => 'client-docs/' . $uuid . '.pdf',
    'ContentType' => 'application/pdf',
]);

$request = $s3->createPresignedRequest($cmd, '+10 minutes');
$presignedUrl = (string) $request->getUri();

// Return JSON: { "url": "...", "key": "client-docs/..." }

The browser sends PUT to that URL with the file body and matching Content-Type header. Your API records the pending upload when issuing the URL. After upload, verify with headObject or an S3 event notification before marking the document as available.

On a legal-tech client portal I built, presigned uploads kept PDF evidence off the app server. That reduced attack surface and matched how Mijar Law Associates client portal handles sensitive documents. Same pattern applies to any high-volume media workflow like Quick And Easy Nepalese Grocery product images.

How do you secure Amazon S3 file uploads and avoid production failures?

Upload code that works in staging fails in production for predictable reasons. Lock these down before launch.

IAM least privilege

Grant only the actions you need. A typical app user policy:

{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Action": [
      "s3:PutObject",
      "s3:GetObject",
      "s3:DeleteObject",
      "s3:AbortMultipartUpload",
      "s3:ListMultipartUploadParts"
    ],
    "Resource": "arn:aws:s3:::my-app-uploads/uploads/*"
  }]
}

Do not attach s3:* on arn:aws:s3:::* to application credentials. Rotate keys if they ever leak. Prefer IAM roles over static keys on EC2—see hosting Laravel on AWS EC2 with RDS and S3.

Block public access and use bucket policies

Enable S3 Block Public Access at the account and bucket level. Serve files through CloudFront with signed URLs if users need download links. Never set public-read ACL on user uploads—that is an open invitation for malware hosting.

Encryption

Default to SSE-S3 (AES256) or SSE-KMS for audit trails. KMS adds per-request cost but fits regulated document storage. Details in AWS KMS envelope encryption explained.

CORS for browser uploads

[
  {
    "AllowedHeaders": ["*"],
    "AllowedMethods": ["PUT", "POST", "GET"],
    "AllowedOrigins": ["https://yourdomain.com"],
    "ExposeHeaders": ["ETag"],
    "MaxAgeSeconds": 3000
  }
]

Restrict AllowedOrigins to your real domains. Wildcard * on a bucket that accepts presigned PUT is risky.

Error handling checklist

  1. Catch S3Exception (PHP) or S3ServiceException (Node) and log the AWS error code—not just "upload failed"
  2. Retry transient errors (503 SlowDown, timeouts); the SDK retries by default but custom wrappers should not swallow exceptions
  3. Validate MIME type with file content inspection, not only the client-supplied header
  4. Set max upload size in Nginx/Apache and in application validation
  5. After deploy, confirm PHP-FPM opcache and the correct IAM role are active—I've seen uploads fail because staging keys were still in .env

Official references: the AWS SDK for PHP S3 multipart upload guide and the Amazon S3 upload objects documentation cover API limits and edge cases. For signature version details, see the AWS Signature Version 4 signing spec.

If uploads are one piece of a larger API surface, our API development service and SDK design for public APIs article cover versioning and idempotency patterns that pair well with S3 object keys.

Need to debug encoded payloads during integration? The Base64 encoder and decoder helps inspect small test files without writing throwaway scripts.

Ongoing upload failures after infrastructure changes often need server-level review—Linux system administration and support and maintenance cover the ops side when the SDK code itself is correct.

Key Takeaways

  • Configure the AWS SDK with region, least-privilege IAM, and never commit access keys to git.
  • Use PutObject for small files; switch to multipart or TransferManager/lib-storage for large streams.
  • Presigned URLs offload bandwidth to S3—validate auth before signing and confirm upload server-side.
  • Sanitise object keys, enforce Content-Type, and block public ACLs on user-generated content.
  • Set lifecycle rules to abort stale multipart uploads and control storage cost.
  • Pair SDK uploads with upload security validation and encryption defaults on every bucket.

People Also Ask

What is the maximum file size for a single PutObject upload?

Amazon S3 accepts up to 5 GB in one PutObject request. In practice, use multipart upload above 100 MB so your app streams from disk and survives network blips without holding the entire file in memory.

Can the AWS SDK upload to S3 without access keys?

Yes. On EC2, ECS, or Lambda, attach an IAM role. The SDK loads temporary credentials from the instance metadata service or container credentials automatically. No keys in environment variables are required.

How do I upload to S3 from Laravel without writing raw SDK code?

Configure the s3 disk in config/filesystems.php and call Storage::disk('s3')->put(). Laravel uses the AWS SDK for PHP internally. Custom presigned URL logic still uses the underlying S3 client when needed.

Is boto3 the same as the AWS SDK?

boto3 is the AWS SDK for Python. It wraps the same S3 APIs as aws-sdk-php and @aws-sdk/client-s3. Concepts—buckets, keys, multipart, presigned URLs—transfer directly across languages.

Ship reliable S3 uploads on your next project

Amazon S3 file uploads with the AWS SDK are straightforward once credentials, bucket policy, and upload method match your file sizes and traffic pattern. Start with PutObject and SSE encryption, add presigned URLs when browser uploads saturate your servers, and enforce multipart with lifecycle cleanup before you launch large-file features. If you want help wiring S3 into a Laravel app, document portal, or eCommerce catalog on AWS, contact us or browse custom software development and the project portfolio for examples of production file workflows. For broader cloud architecture context, read AWS vs DigitalOcean vs Hetzner for Laravel hosting and about the author.

Frequently Asked Questions

The AWS SDK is Amazon's official client library for your runtime. It signs HTTP requests, retries with exponential backoff, and exposes methods like putObject and upload so you never hand-roll SigV4 calls to s3.amazonaws.com. It serialises file bytes or streams into S3 objects with a bucket, key, metadata, permissions, and optional server-side encryption.

Amazon S3 accepts up to 5 GB in one PutObject request. In practice, use multipart upload above roughly 100 MB so your app streams from disk instead of loading the entire file into memory.

Yes. On EC2, ECS, or Lambda, attach an IAM role. The SDK loads temporary credentials from instance metadata or container credentials automatically—no keys in environment variables required.

For direct SDK usage outside Flysystem, run composer require aws/aws-sdk-php:^3.0. Laravel 12 and 13 ship Flysystem S3 support using that package internally. Create an S3Client with version latest, your region such as ap-south-1, and credentials from environment variables AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and AWS_DEFAULT_REGION. PHP 8.3 or higher is required for Laravel 13; PHP 8.2 works with Laravel 12. On production, store secrets in AWS Secrets Manager rather than plain .env files when compliance matters.

For Node.js, install @aws-sdk/client-s3 and @aws-sdk/lib-storage, targeting Node.js 26 LTS or 24 LTS. Use PutObjectCommand for small files and the Upload helper from lib-storage for streaming multipart uploads. For Python, pip install boto3—it picks up ~/.aws/credentials or environment variables automatically. Use client.upload_file with ExtraArgs for ContentType and ServerSideEncryption. Concepts transfer directly across all three runtimes.

Multipart upload splits large files into parts of at least 5 MB each, uploads them in parallel, and retries failed parts without restarting the whole file. Use PutObject for files under roughly 100 MB. Above that threshold, use PHP TransferManager, Node.js @aws-sdk/lib-storage Upload, or boto3's built-in multipart config. A common production bug is orphaned multipart uploads after client disconnects—set a bucket lifecycle rule to abort incomplete uploads after seven days.

PutObject is the simplest single-request upload. Pass Bucket, Key, the file body or SourceFile path, ContentType, and ServerSideEncryption AES256. In PHP, call $s3->putObject with a sanitised key like uploads/ plus a safe filename—not the client's original name. Always strip path traversal, normalise Unicode, and generate your own filename. Set metadata such as uploaded-by for audit trails. Catch S3Exception and log the AWS error code, not just upload failed.

Presigned URLs let the browser PUT or POST straight to S3 with a time-limited signature your backend generates, offloading bandwidth from PHP-FPM or Node workers. In PHP, build a PutObject command, call createPresignedRequest with an expiry like plus ten minutes, and return the URL and key as JSON. The browser sends PUT with the file body and matching Content-Type header. Your API records the pending upload when issuing the URL, then verify with headObject or an S3 event notification before marking the document available.

Grant least privilege only. A typical app policy allows s3:PutObject, s3:GetObject, s3:DeleteObject, s3:AbortMultipartUpload, and s3:ListMultipartUploadParts on arn:aws:s3:::my-app-uploads/uploads/—not s3: on all buckets. Do not attach broad s3:* permissions to application credentials. Rotate keys if they leak. Prefer IAM instance profiles or ECS task roles over static keys on EC2. After deploy, confirm the correct IAM role is active—I have seen uploads fail because staging keys were still in .env.

Enable S3 Block Public Access at account and bucket level—never set public-read ACL on user uploads. Default to SSE-S3 AES256 or SSE-KMS for regulated document storage. Validate MIME type with file content inspection, not only the client header. Set max upload size in Nginx or Apache and in application validation. Restrict CORS AllowedOrigins to your real domains, not wildcard on buckets accepting presigned PUT. Retry transient errors like 503 SlowDown; the SDK retries by default but custom wrappers should not swallow exceptions.

Your bucket needs a CORS rule allowing PUT, POST, and GET from your application origin. A typical config sets AllowedHeaders to star, AllowedMethods to PUT, POST, and GET, AllowedOrigins to https://yourdomain.com, ExposeHeaders to ETag, and MaxAgeSeconds to 3000. Restrict AllowedOrigins to your real domains—wildcard star on a bucket that accepts presigned PUT is risky. The browser must send a Content-Type header matching what was signed when the presigned URL was generated.

Configure the s3 disk in config/filesystems.php with your AWS credentials and bucket, then call Storage::disk('s3')->put(). Laravel 12 and 13 use the AWS SDK for PHP internally via Flysystem. For standard file storage, visibility, and URL generation, the filesystem abstraction is enough. Custom presigned URL logic for direct browser uploads still uses the underlying S3Client when needed—the SDK layer is the same whether you call it directly or through Laravel's wrapper.

boto3 is the AWS SDK for Python. It wraps the same S3 APIs as aws/aws-sdk-php and @aws-sdk/client-s3 in Node.js. Buckets, keys, multipart uploads, presigned URLs, and server-side encryption concepts transfer directly across languages. Only the syntax differs—boto3.client upload_file with ExtraArgs versus PHP putObject or Node PutObjectCommand. Pick boto3 for Python workers and data pipelines; use the PHP or Node SDK packages for web application runtimes.

Nepal-based apps often use ap-south-1 Mumbai for lower latency and predictable data residency within AWS's published region list. Set AWS_DEFAULT_REGION or pass region_name when creating your S3 client. Region choice affects latency for uploads and downloads from Kathmandu. If egress fees dominate your bill after choosing a region, cross-check pricing against Cloudflare R2 versus AWS S3 cost breakdown before committing large media or export workflows to a specific provider.

Use MinIO or another S3-compatible storage endpoint for local development. The same AWS SDK calls work when you configure a custom endpoint instead of the default s3.amazonaws.com URL. Set credentials via environment variables or the shared credentials file at ~/.aws/credentials. This lets you validate upload logic, key sanitisation, and multipart behaviour before deploying to production buckets with IAM roles and real encryption policies on EC2 or ECS.

Share this article

0 Comments

Leave a comment

Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

Quick Contact Options
Choose how you want to connect me: