PHPackages                             appoly/s3-uploader - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [File &amp; Storage](/categories/file-storage)
4. /
5. appoly/s3-uploader

ActiveLibrary[File &amp; Storage](/categories/file-storage)

appoly/s3-uploader
==================

S3 multipart uploader for Laravel

v1.0.0(2mo ago)063—0%MITPHPPHP ^8.2

Since Jan 16Pushed 2mo agoCompare

[ Source](https://github.com/appoly/s3-uploader)[ Packagist](https://packagist.org/packages/appoly/s3-uploader)[ RSS](/packages/appoly-s3-uploader/feed)WikiDiscussions main Synced 1mo ago

READMEChangelogDependencies (2)Versions (4)Used By (0)

🚀 S3 Uploader for Laravel
=========================

[](#-s3-uploader-for-laravel)

[![Latest Version on Packagist](https://camo.githubusercontent.com/34edfc281413fc7d78e59065d2a296f2346a9d69d3d172bd50ca7b7e1ec4d1fb/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f6170706f6c792f73332d75706c6f616465722e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/appoly/s3-uploader)[![Total Downloads](https://camo.githubusercontent.com/3bb287109bda835a5282764c2a9e52ce74c813bd0b1ee5598c87f3cc01ce883d/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f6170706f6c792f73332d75706c6f616465722e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/appoly/s3-uploader)[![License](https://camo.githubusercontent.com/1b4980c8c554fce55263d61d7ed5b0305016c5a1cca2d720c35867a7cebf93cb/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f6c2f6170706f6c792f73332d75706c6f616465722e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/appoly/s3-uploader)

A Laravel package for handling S3 multipart uploads with presigned URLs. Perfect for uploading large files directly from the browser to S3.

---

✨ Features
----------

[](#-features)

- 📤 **Multipart uploads** - Upload large files in chunks
- 🔐 **Presigned URLs** - Secure, time-limited upload URLs
- ⚡ **Direct to S3** - Browser uploads directly to S3, bypassing your server
- 🎛️ **Configurable** - Flexible configuration options
- 🛣️ **Optional routes** - Use built-in routes or define your own
- 🏗️ **Facade &amp; DI** - Use however you prefer

---

📦 Installation
--------------

[](#-installation)

```
composer require appoly/s3-uploader
```

---

⚙️ Configuration
----------------

[](#️-configuration)

Publish the config file:

```
php artisan vendor:publish --tag=s3-uploader-config
```

By default, the package uses your `s3` filesystem disk configuration. Override in `config/s3-uploader.php` or via environment variables.

### 🔑 Environment Variables

[](#-environment-variables)

```
# Optional - defaults to your S3 disk settings
S3_UPLOADER_BUCKET=your-bucket
S3_UPLOADER_REGION=eu-west-2
S3_UPLOADER_KEY=your-key
S3_UPLOADER_SECRET=your-secret
S3_UPLOADER_ENDPOINT=
S3_UPLOADER_PATH_STYLE=false

# Customization
S3_UPLOADER_PATH_PREFIX=uploads/multipart
S3_UPLOADER_URL_EXPIRATION="+60 minutes"
S3_UPLOADER_WRAP_RESPONSES=false
```

VariableDefaultDescription`S3_UPLOADER_BUCKET`S3 disk bucketS3 bucket name`S3_UPLOADER_REGION`S3 disk regionAWS region (e.g., `eu-west-2`)`S3_UPLOADER_KEY`S3 disk keyAWS Access Key ID`S3_UPLOADER_SECRET`S3 disk secretAWS Secret Access Key`S3_UPLOADER_ENDPOINT`S3 disk endpointCustom endpoint for S3-compatible services (MinIO, DigitalOcean Spaces, etc.)`S3_UPLOADER_PATH_STYLE``false`Use path-style URLs instead of virtual-hosted style (required for some S3-compatible services)`S3_UPLOADER_PATH_PREFIX``uploads/multipart`Prefix path for uploaded files in S3`S3_UPLOADER_URL_EXPIRATION``+60 minutes`How long presigned URLs remain valid`S3_UPLOADER_WRAP_RESPONSES``false`Wrap responses in `{success, message, data}` envelope---

🚀 Usage
-------

[](#-usage)

### Using the Facade

[](#using-the-facade)

```
use Appoly\S3Uploader\Facades\S3Uploader;

// 1️⃣ Initiate upload
$upload = S3Uploader::initiateMultipartUpload('video.mp4', 'video/mp4');
// Returns: ['upload_id' => '...', 'file_path' => '...']

// 2️⃣ Get presigned URL for each part
$part = S3Uploader::getPresignedUrlForPart(
    $upload['upload_id'],
    $upload['file_path'],
    partNumber: 1
);
// Returns: ['presigned_url' => '...', 'part_number' => 1, 'headers' => []]

// 3️⃣ Complete upload (after all parts uploaded)
$result = S3Uploader::completeMultipartUpload(
    $upload['upload_id'],
    $upload['file_path'],
    $parts // [['part_number' => 1, 'etag' => '...'], ...]
);
// Returns: ['file_path' => '...', 'location' => '...']

// ❌ Abort upload (if needed)
S3Uploader::abortMultipartUpload($upload['upload_id'], $upload['file_path']);
```

### Using Dependency Injection

[](#using-dependency-injection)

```
use Appoly\S3Uploader\Services\MultipartUploadService;

class UploadController
{
    public function __construct(
        private MultipartUploadService $uploader
    ) {}

    public function start()
    {
        return $this->uploader->initiateMultipartUpload('document.pdf', 'application/pdf');
    }
}
```

---

### 🗄️ Config Options

[](#️-config-options)

You can also configure the package directly in `config/s3-uploader.php`:

```
return [
    // Use a specific Laravel filesystem disk for credentials (set to null to use explicit credentials)
    'disk' => 's3',

    // S3 credentials (falls back to disk settings if not specified)
    'bucket' => env('S3_UPLOADER_BUCKET'),
    'region' => env('S3_UPLOADER_REGION'),
    'key' => env('S3_UPLOADER_KEY'),
    'secret' => env('S3_UPLOADER_SECRET'),
    'endpoint' => env('S3_UPLOADER_ENDPOINT'),
    'use_path_style_endpoint' => env('S3_UPLOADER_PATH_STYLE', false),

    // Upload settings
    'path_prefix' => env('S3_UPLOADER_PATH_PREFIX', 'uploads/multipart'),
    'presigned_url_expiration' => env('S3_UPLOADER_URL_EXPIRATION', '+60 minutes'),

    // Wrap responses in {success, message, data} envelope
    'wrap_responses' => env('S3_UPLOADER_WRAP_RESPONSES', false),

    // Route settings
    'routes' => [
        'enabled' => true,
        'prefix' => 'api/s3/multipart',
        'middleware' => ['api'],
    ],
];
```

---

🛣️ API Endpoints
----------------

[](#️-api-endpoints)

The package registers these routes by default (can be customized via `routes.prefix` and `routes.middleware`):

MethodEndpointRoute NameDescription`POST``/api/s3/multipart/initiate``s3-uploader.initiate`Start a multipart upload`POST``/api/s3/multipart/presign-part``s3-uploader.presign-part`Get presigned URL for a part`POST``/api/s3/multipart/complete``s3-uploader.complete`Complete the upload`POST``/api/s3/multipart/abort``s3-uploader.abort`Abort the upload---

### POST `/api/s3/multipart/initiate`

[](#post-apis3multipartinitiate)

Start a new multipart upload session.

**Headers:**

HeaderRequiredValue`Content-Type`Yes`application/json``Accept`Yes`application/json`**Request Body:**

ParameterTypeRequiredDescription`file_name`stringYesOriginal filename (e.g., `video.mp4`)`content_type`stringNoMIME type (default: `application/octet-stream`)**Example Request:**

```
{
    "file_name": "video.mp4",
    "content_type": "video/mp4"
}
```

**Example Response:**

```
{
    "upload_id": "abc123def456...",
    "file_path": "uploads/multipart/550e8400-e29b-41d4-a716-446655440000-video.mp4"
}
```

---

### POST `/api/s3/multipart/presign-part`

[](#post-apis3multipartpresign-part)

Get a presigned URL to upload a specific part directly to S3.

**Headers:**

HeaderRequiredValue`Content-Type`Yes`application/json``Accept`Yes`application/json`**Request Body:**

ParameterTypeRequiredDescription`upload_id`stringYesUpload ID from initiate response`file_path`stringYesFile path from initiate response`part_number`integerYesPart number (starts at 1)**Example Request:**

```
{
    "upload_id": "abc123def456...",
    "file_path": "uploads/multipart/550e8400-e29b-41d4-a716-446655440000-video.mp4",
    "part_number": 1
}
```

**Example Response:**

```
{
    "presigned_url": "https://bucket.s3.region.amazonaws.com/...",
    "part_number": 1,
    "headers": {}
}
```

---

### POST `/api/s3/multipart/complete`

[](#post-apis3multipartcomplete)

Complete the multipart upload after all parts have been uploaded to S3.

**Headers:**

HeaderRequiredValue`Content-Type`Yes`application/json``Accept`Yes`application/json`**Request Body:**

ParameterTypeRequiredDescription`upload_id`stringYesUpload ID from initiate response`file_path`stringYesFile path from initiate response`parts`arrayYesArray of uploaded parts`parts.*.part_number`integerYesPart number`parts.*.etag`stringYesETag returned by S3 after uploading the part**Example Request:**

```
{
    "upload_id": "abc123def456...",
    "file_path": "uploads/multipart/550e8400-e29b-41d4-a716-446655440000-video.mp4",
    "parts": [
        { "part_number": 1, "etag": "\"a54357aff0632cce46d942af68356b38\"" },
        { "part_number": 2, "etag": "\"0c78aef83f66abc1fa1e8477f296d394\"" }
    ]
}
```

**Example Response:**

```
{
    "file_path": "uploads/multipart/550e8400-e29b-41d4-a716-446655440000-video.mp4",
    "location": "https://bucket.s3.region.amazonaws.com/uploads/multipart/550e8400-e29b-41d4-a716-446655440000-video.mp4"
}
```

---

### POST `/api/s3/multipart/abort`

[](#post-apis3multipartabort)

Abort an in-progress multipart upload and clean up any uploaded parts.

**Headers:**

HeaderRequiredValue`Content-Type`Yes`application/json``Accept`Yes`application/json`**Request Body:**

ParameterTypeRequiredDescription`upload_id`stringYesUpload ID from initiate response`file_path`stringYesFile path from initiate response**Example Request:**

```
{
    "upload_id": "abc123def456...",
    "file_path": "uploads/multipart/550e8400-e29b-41d4-a716-446655440000-video.mp4"
}
```

**Example Response:**

```
{
    "success": true
}
```

---

📦 Response Wrapping
-------------------

[](#-response-wrapping)

By default, endpoints return flat JSON responses. Enable `wrap_responses` to wrap all responses in a standard envelope — useful for APIs that follow a `{success, message, data}` convention.

```
// config/s3-uploader.php
'wrap_responses' => true,
```

**Wrapped success response:**

```
{
    "success": true,
    "message": null,
    "data": {
        "upload_id": "abc123def456...",
        "file_path": "uploads/multipart/550e8400-video.mp4"
    }
}
```

**Wrapped error response (500):**

```
{
    "success": false,
    "message": "Error connecting to S3",
    "data": {
        "error": "Error connecting to S3"
    }
}
```

When `wrap_responses` is `false` (default), responses are returned as flat JSON and errors are returned as standard Laravel exception responses.

---

🎨 Customizing Routes
--------------------

[](#-customizing-routes)

Disable default routes and define your own:

```
// config/s3-uploader.php
'routes' => [
    'enabled' => false,
],
```

Then in your routes file:

```
use Appoly\S3Uploader\Http\Controllers\MultipartUploadController;

Route::middleware(['auth:sanctum'])->prefix('api/uploads')->group(function () {
    Route::post('/start', [MultipartUploadController::class, 'initiate']);
    Route::post('/sign', [MultipartUploadController::class, 'presignPart']);
    Route::post('/finish', [MultipartUploadController::class, 'complete']);
    Route::post('/cancel', [MultipartUploadController::class, 'abort']);
});
```

---

🌐 Frontend Example
------------------

[](#-frontend-example)

Here's a basic JavaScript example for uploading:

```
async function uploadFile(file) {
    const CHUNK_SIZE = 5 * 1024 * 1024; // 5MB minimum for S3
    const totalParts = Math.ceil(file.size / CHUNK_SIZE);

    // 1️⃣ Initiate
    const { upload_id, file_path } = await fetch('/api/s3/multipart/initiate', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
            file_name: file.name,
            content_type: file.type
        })
    }).then(r => r.json());

    // 2️⃣ Upload parts
    const parts = [];
    for (let partNumber = 1; partNumber  r.json());

        // Upload chunk directly to S3
        const response = await fetch(presigned_url, { method: 'PUT', body: chunk });
        parts.push({ part_number: partNumber, etag: response.headers.get('ETag') });
    }

    // 3️⃣ Complete
    return fetch('/api/s3/multipart/complete', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ upload_id, file_path, parts })
    }).then(r => r.json());
}
```

---

🏢 Credits
---------

[](#-credits)

- [Appoly](https://github.com/appoly)

---

 Made with ❤️ by [Appoly](https://appoly.co.uk)

###  Health Score

41

—

FairBetter than 89% of packages

Maintenance84

Actively maintained with recent releases

Popularity12

Limited adoption so far

Community8

Small or concentrated contributor base

Maturity49

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 80% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~35 days

Total

2

Last Release

82d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/69684a203a56f31f41383ce7bc7be23cdf2140a36eac7a6558bda31c1b53924b?d=identicon)[Mezzair](/maintainers/Mezzair)

---

Top Contributors

[![asdbsd](https://avatars.githubusercontent.com/u/6268694?v=4)](https://github.com/asdbsd "asdbsd (8 commits)")[![projectdelta6](https://avatars.githubusercontent.com/u/10919627?v=4)](https://github.com/projectdelta6 "projectdelta6 (2 commits)")

---

Tags

laravels3awsmultipartuploadappoly

### Embed Badge

![Health badge](/badges/appoly-s3-uploader/health.svg)

```
[![Health](https://phpackages.com/badges/appoly-s3-uploader/health.svg)](https://phpackages.com/packages/appoly-s3-uploader)
```

###  Alternatives

[aws/aws-sdk-php-laravel

A simple Laravel 9/10/11/12/13 service provider for including the AWS SDK for PHP.

1.7k35.6M75](/packages/aws-aws-sdk-php-laravel)[vinelab/cdn

Content Delivery Network (CDN) Package for Laravel

217240.8k1](/packages/vinelab-cdn)[publiux/laravelcdn

Content Delivery Network (CDN) Package for Laravel

155230.4k](/packages/publiux-laravelcdn)[juhasev/laravelcdn

Content Delivery Network (CDN) Package for Laravel

1820.4k](/packages/juhasev-laravelcdn)[unisharp/s3-presigned

An AWS S3 package for pre-signed upload purpose in Laravel and PHP.

151.8k](/packages/unisharp-s3-presigned)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
