Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Flysystem Aws S3 V2 Laravel Package

league/flysystem-aws-s3-v2

Flysystem adapter for Amazon S3 built on AWS SDK v2. It lets PHP applications store, retrieve, and manage files on S3 through Flysystem’s consistent filesystem API, making it useful for legacy projects still tied to the v2 SDK. Ideal for maintaining older applications with a familiar Flysystem inter...

View on GitHub
Deep Wiki
Context7

Getting Started

Check your stack before you type a command. This adapter is archived, pinned to aws/aws-sdk-php v2 (superseded in 2015) and league/flysystem 1.x. It will not install on a modern Laravel (9+) or Flysystem 3 project. If you are starting fresh, close this tab and use league/flysystem-aws-s3-v3 or Laravel's native S3 driver. This package is for one scenario only: an existing, pinned legacy application that already ships on Flysystem 1.x + SDK v2.

Minimal install for such an app:

composer require league/flysystem-aws-s3-v2:"^1.0"

Do not force league/flysystem or aws/aws-sdk-php up a major version to satisfy other packages — the adapter calls SDK v2 signatures (upload, getObject, headObject, copyObject) that were removed or renamed in v3. Composer constraints here are load-bearing.

Read these first, in this order: src/AwsS3Adapter.php (it is small — __construct, upload, read, getMetadata cover ~90% of behaviour), then the Flysystem 1.x Filesystem/AdapterInterface docs, then the SDK v2 S3Client factory docs. Everything you'll debug lives in those three places.

First use case — put and get an object:

use Aws\S3\S3Client;
use League\Flysystem\Filesystem;
use League\Flysystem\AwsS3v2\AwsS3Adapter;

$client = S3Client::factory([
    'key'    => env('AWS_KEY'),
    'secret' => env('AWS_SECRET'),
    'region' => 'eu-west-1',   // v2 style: a string, not ['region' => '...']
]);

$adapter = new AwsS3Adapter($client, 'my-bucket', 'optional/key/prefix');
$fs = new Filesystem($adapter);

$fs->write('hello.txt', 'world', ['visibility' => 'public']);
echo $fs->read('hello.txt');

In Laravel, remember that 'driver' => 's3' will not use this package — the framework's built-in driver points at the v3 adapter. You register a custom driver name yourself (see Implementation Patterns).

Implementation Patterns

Laravel custom disk driver. Register it in a service provider and reference it by name in config:

// AppServiceProvider::boot()
Storage::extend('s3v2', function ($app, $config) {
    $client = \Aws\S3\S3Client::factory([
        'key'    => $config['key'],
        'secret' => $config['secret'],
        'region' => $config['region'],
    ]);

    return new \League\Flysystem\Filesystem(
        new \League\Flysystem\AwsS3v2\AwsS3Adapter(
            $client,
            $config['bucket'],
            $config['root'] ?? ''
        )
    );
});
// config/filesystems.php
'disks' => [
    'media' => [
        'driver' => 's3v2',
        'key'    => env('AWS_KEY'),
        'secret' => env('AWS_SECRET'),
        'region' => env('AWS_REGION'),
        'bucket' => env('AWS_BUCKET'),
        'root'   => 'uploads',
    ],
],

Because Storage returns a Filesystem, application code stays identical to any other disk — that's the whole point, and your migration escape hatch. Keep business logic on Storage::disk('media') and never use the adapter class outside the provider.

Prefix-per-tenant / prefix-per-environment. The adapter's third constructor argument is an object-key prefix (not an S3 subfolder). Lean on it: resolve a disk per tenant or per environment with the prefix injected at resolution time, so listContents is scoped and IAM/bucket policies can target prefix/*. This is safer than sprintf-ing paths throughout the app and simplifies future cleanup.

Streams for large files. Prefer writeStream/readStream over write/read to avoid loading payloads into memory. The adapter passes streams to SDK v2 putObject/getObject. Note: the adapter's write does not transparently trigger S3 multipart the way the SDK's MultiPartUploader or the v3 adapter's putObject with mup_threshold does — for anything above a few tens of MB, drive the multipart upload directly with the SDK, then reconcile via has()/getMetadata() through the filesystem so other code sees a consistent view.

Visibility. Two models coexist: legacy ACLs (['visibility' => 'public'] → public-read) and modern bucket policies. Pick one per bucket. Passing visibility while relying on a bucket policy is fine and harmless, but passing private while the bucket is public-read-policy'd will confuse everyone. Keep it explicit and documented in the disk config.

Caching wrapper. Wrap with League\Flysystem\Cached\CachedAdapter for read-heavy paths. Every has()/getMetadata() is an HTTP round trip to S3, so exists()-then-put() patterns double your latency. The cache pays for itself immediately.

Local S3 for tests and CI. Point the client at MinIO/LocalStack using the v2 key base_url — not v3's endpoint:

S3Client::factory([
    'base_url' => 'http://localhost:9000',
    'region'   => 'us-east-1',
    'key'      => 'x',
    'secret'   => 'x',
]);

Keep this in a test service provider so production config is untouched.

Isolate behind your own interface. Wrap Storage::disk(...) in a thin MediaStore/DocumentStore class. When you migrate to v3 (or Laravel's native S3), the adapter swap happens in one place, the app keeps calling $store->put(...), and rollback is a config change rather than a code revert.

Gotchas and Tips

This package is dead — treat it as a known liability, not a feature. No security patches, no bug fixes, no compatibility updates. Pin the exact version, track it in your dependency-risk register, and open a migration ticket the same day you adopt it. If you're not prepared to do that, you're not prepared to use it.

Version pinning is non-negotiable. composer update on a floating constraint can pull a Flysystem 2/3 transitive from another package and hard-break this adapter with cryptic signature errors (e.g. call_user_func_array() expects parameter 1 to be a valid callback). Pin league/flysystem, league/flysystem-aws-s3-v2, and aws/aws-sdk-php to exact versions in composer.json. Run composer why before any update.

Credential resolution gaps. SDK v2's credential chain does not support modern IAM task/instance roles with the same ergonomics as v3. On ECS/EKS you'll likely end up using environment variables or a mounted credentials file, which is fragile. Audit the credential path before assuming "it works in prod like dev." Rotate legacy keys aggressively.

Config key mismatches v2 ↔ v3. Frequent copy-paste errors: v2 uses base_url (not endpoint), region => 'string' (not array), key/secret (not credentials array). If you're adapting a snippet from a blog or a v3 migration guide, check it twice.

Endpoint / region / signature drift. Custom S3-compatible endpoints work via base_url but require explicit region; leave it off and signing can fail or default unpredictably. Old region names still resolve but some newer ones may not — if you're on a newer region, test against the live endpoint, not just MinIO.

Silent failures on metadata. getMetadata uses headObject. A 403 (rather than 404) will surface as a thrown SDK exception rather than false. In practice this means an IAM misconfiguration looks like "file not found" wrapped in an exception. Catch S3Exception, log the status code, and consider mapping 403 → a distinct error path so your monitoring doesn't page the wrong team.

Presigned URLs are limited. The adapter predates full v3 presigned-URL ergonomics. If your product needs time-limited download links, expect to call the SDK's getObjectUrl/createPresignedUrl directly with the same credential context — and don't assume it's tested by the adapter's own suite.

listContents performance. It paginates under the hood, but each page is a separate request and each object is returned as an array. On buckets with tens of thousands of keys, this is slow and memory-hungry. Use the SDK's ListObjectsV2 iterator directly for large listings and only reconcile through the filesystem for single-object ops.

No multipart retry semantics. If a large upload fails halfway, you don't get the SDK's automatic retry-with-multipart that v3 users take for granted. For anything mission-critical or >50 MB, wrap in your own retry logic and consider a background job rather than an inline request — a slow S3 round trip in a request is a timeout risk.

Watch for PHP deprecation noise. The adapter and SDK v2 predate PHP 8 strictness (non-nullable params, dynamic properties, each() deprecations). On PHP 8 you'll see warnings in logs and potentially fatal errors in the SDK. If your error_reporting is strict, you'll spend time filtering noise — but do not silence it entirely: some of those warnings are real signal about imminent breakage.

Testing hygiene. Point CI at MinIO. Do not run integration tests against real AWS — costs, flakiness, and credentials in CI leak. Separate a fast unit tier (mocked Storage interface) from a slow integration tier (MinIO + a small fixture set).

Extension points, if you must. Subclass AwsS3Adapter and override upload/getMetadata only to add logging or metrics — anything deeper is fighting the abandoned design. For behaviour changes, wrap the Filesystem, not the adapter.

The migration you should already be scoping. Buckets, keys, and object layout survive the switch to league/flysystem-aws-s3-v3 or Laravel's native S3 driver untouched — the work is dependency, config, and credential modernization, not data movement. The abstraction is your insurance policy; use it. Freeze new features on this adapter the moment you adopt it.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin