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

S3 Bundle Laravel Package

jplarar/s3-bundle

A lightweight Laravel bundle that wraps AWS S3 storage, providing simple configuration and helpers for uploading, retrieving, and managing files on S3-compatible services. Integrates with Laravel's filesystem for quick cloud storage setup.

View on GitHub
Deep Wiki
Context7

Getting Started

Read this first: jplarar/s3-bundle is a Symfony 2 bundle, not a Laravel package. It has no service provider, no facade, no package discovery, and its composer.json will constrain symfony/symfony to the 2.x line — which conflicts with every modern Laravel install. You will not composer require this into a Laravel app and get a working result. Last release was 2019-06-11, with zero stars and zero dependents, so treat it as a reference implementation rather than a dependency.

With that framing, here is where to look and what to do:

  1. Open composer.json — confirm the Symfony and AWS SDK version constraints. This tells you which Aws\S3\S3Client API surface the bundle was written against (v2 vs v3), which determines whether its method calls map cleanly onto what you'd write today.
  2. Open DependencyInjection/Configuration.php — this is the config tree (key, secret, region, bucket, possibly endpoint). It is the only genuinely useful artifact: it documents the minimum credentials set you need.
  3. Open Resources/config/services.yml — find the service id and the class it points at. The bundle is almost certainly a thin wrapper: construct an S3Client, expose putObject/getObject/deleteObject/getObjectUrl helpers. That's the entire value proposition.

Your first use case in Laravel is not this bundle — it's the native disk:

composer require league/flysystem-aws-s3-v3 "^3.0"
// config/filesystems.php
's3' => [
    'driver' => 's3',
    'key' => env('AWS_ACCESS_KEY_ID'),
    'secret' => env('AWS_SECRET_ACCESS_KEY'),
    'region' => env('AWS_DEFAULT_REGION'),
    'bucket' => env('AWS_BUCKET'),
    'url' => env('AWS_URL'),
    'endpoint' => env('AWS_ENDPOINT'),
    'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false),
],
Storage::disk('s3')->put('avatars/1.png', $contents);

That one call replaces the bundle's putObject wrapper, handles retries, and is maintained. The bundle's source is worth 10 minutes of reading only if you're porting an existing Symfony 2 app or need to understand a legacy integration.

Implementation Patterns

Pattern 1 — Native Storage facade (the default, 95% of cases). Everything the bundle does, Laravel already does:

Storage::disk('s3')->putFileAs('exports', $request->file('report'), 'q4.csv', 'private');
Storage::disk('s3')->temporaryUrl('exports/q4.csv', now()->addMinutes(15));
Storage::disk('s3')->exists('exports/q4.csv');
Storage::disk('s3')->deleteDirectory('tmp/12345');

Pattern 2 — Raw S3Client bound in a service provider. When you genuinely need SDK features the facade doesn't expose (multipart tuning, object tagging, CopyObject with metadata directives, S3 Select), inject the client directly rather than porting the bundle's wrapper:

// app/Providers/AwsServiceProvider.php
$this->app->singleton(S3Client::class, fn () => new S3Client([
    'version' => 'latest',
    'region'  => config('filesystems.disks.s3.region'),
    'endpoint' => config('filesystems.disks.s3.endpoint'),
    'use_path_style_endpoint' => config('filesystems.disks.s3.use_path_style_endpoint'),
    // omit key/secret to use the default credential chain (IAM role, SSO, env)
]));

Get the already-constructed client from the disk instead of building a second one:

$client = Storage::disk('s3')->getClient();

Pattern 3 — Presigned direct-to-S3 uploads. This is the pattern that actually saves you money and time in production; the bundle predates it being easy. Issue a presigned PutObject from the server, upload from the browser, then confirm server-side:

$cmd = $client->getCommand('PutObject', [
    'Bucket' => config('filesystems.disks.s3.bucket'),
    'Key'    => "uploads/{$uuid}",
]);
$url = $client->createPresignedRequest($cmd, '+10 minutes')->getUri();

Pattern 4 — Legacy Symfony 2 interop. If you're keeping a Symfony app on this bundle while migrating to Laravel, keep them pointed at the same bucket but namespace keys by application (legacy/... vs app/...) and centralize credentials in environment variables on both sides. Never let the two apps write to overlapping prefixes during the migration window.

Integration tips. Push large transfers (putFileAs on >50 MB, zip generation, image resizing) onto a queued job so the request doesn't time out. Use Storage::disk('s3')->url() behind CloudFront by setting AWS_URL to the distribution domain, and only fall back to temporaryUrl() for private objects. In tests, Storage::fake('s3') covers existence/write assertions but does not emulate presigned URLs or ACL semantics — keep one integration test against MinIO for those paths.

Gotchas and Tips

It is unmaintained, full stop. Zero stars, zero dependents, no release in six years. Even if you're on Symfony 2, the pinned AWS SDK version will lack support for newer regions and signature requirements. Vendor the code and own it, or replace it.

Dependency conflicts, not errors. Trying to install in Laravel usually fails at composer update with a Symfony constraint conflict rather than a runtime error. If you somehow force it through with --ignore-platform-reqs, you get a bundle that is never booted because Laravel doesn't know what a Bundle class is.

SignatureDoesNotMatch on newer regions. Old AWS SDKs defaulted to Signature v2. Regions created after 2014 (eu-central-1, ap-northeast-2, etc.) require v4. If you port code from the bundle, explicitly set 'signature_version' => 'v4' — modern SDKs default correctly, so this only bites copied legacy snippets.

ACLs are dead. Any bundle-era code doing 'ACL' => 'public-read' per object will fail with AccessControlListNotSupported on buckets with Object Ownership = BucketOwnerEnforced (the default for new buckets since 2023). Use bucket policies plus CloudFront OAI/OAC, or Storage::disk('s3')->setVisibility() only if the bucket still permits ACLs.

Path-style vs virtual-hosted. With MinIO, LocalStack, or any custom endpoint, set use_path_style_endpoint => true. Without it you get DNS resolution failures like uploads.localhost — a frustrating 20 minutes of debugging if you've never hit it.

Storage::url() returning the wrong host. If AWS_URL is unset, Laravel builds https://{bucket}.s3.{region}.amazonaws.com/..., which is correct but bypasses your CDN and any custom domain. Set AWS_URL explicitly.

Memory on large files. Storage::put($path, $contents) loads the whole payload into memory. Use putFile, putFileAs, or a stream resource for anything over a few megabytes; the SDK's multipart uploader handles the chunking once you give it a stream.

Debugging surface. Wrap calls for real error detail — Flysystem swallows the SDK exception into UnableToWriteFile/UnableToReadFile. To see the underlying S3Exception with its AWS error code, either call the client directly or catch and unwrap:

try {
    Storage::disk('s3')->put($key, $stream);
} catch (\League\Flysystem\UnableToWriteFile $e) {
    report($e->getPrevious() ?? $e); // S3Exception has ->getAwsErrorCode()
    throw $e;
}

Extension points worth keeping. Whatever you build, hide the client behind your own interface (ObjectStore with put/url/delete). That's the one lesson the bundle teaches by counterexample: it hard-wires the SDK into a container service, which is precisely why it couldn't be upgraded. In Laravel, bind your interface in a service provider and swap the S3 implementation for a fake in tests.

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