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

Stapler Laravel Package

codesleeve/stapler

Stapler is a Laravel package for handling file uploads and attachments on Eloquent models, with image processing, thumbnails, storage options, and simple configuration for managing uploaded files.

View on GitHub
Deep Wiki
Context7

Getting Started

Stapler attaches files to Eloquent models by declaring a $stapler array directly on the model — there is no migration, no separate trait config file, and no service class to wire up. Install with Composer:

composer require codesleeve/stapler

Then register the provider/facade manually (the package predates Laravel auto-discovery):

// config/app.php
'providers' => [
    Codesleeve\Stapler\StaplerServiceProvider::class,
],
'aliases' => [
    'Stapler' => Codesleeve\Stapler\Facade\Stapler::class,
],

Publish the config so you can see the defaults:

php artisan vendor:publish --provider="Codesleeve\Stapler\StaplerServiceProvider"

The first thing to read is config/stapler.php — it defines default storage (local), the interpolation path template, and the Imagine driver used for image processing. Then look at Codesleeve\Stapler\ORM\StaplerTrait and Codesleeve\Stapler\Attachment; almost everything you'll debug lives in those two.

Minimal model setup:

class User extends Eloquent
{
    use Codesleeve\Stapler\ORM\StaplerTrait,
        Codesleeve\Stapler\ORM\EloquentTrait;

    protected $stapler = [
        'avatar' => [
            'styles' => [
                'thumb' => '80x80#',
                'large' => '500x500>',
            ],
            'storage' => 'local',
            'storageConfig' => [
                'local' => ['path' => 'uploads/avatars'],
            ],
        ],
    ];
}

First upload: retrieve the file from the request, assign it, save the model.

$user = User::find(1);
$user->avatar = Stapler::getFile('avatar'); // input name = attribute name
$user->save();

echo $user->avatar->url('thumb');

If that returns a URL and the resized file exists on disk, everything else is just configuration.

Implementation Patterns

Upload as part of a normal Eloquent save. Stapler hooks the saving/saved events; the assignment only stages the file. Nothing is written to disk until save() runs. This means validation and transaction wrapping work as you'd expect:

$user->fill($request->except('avatar'));
$user->avatar = Stapler::getFile('avatar');

$validator = Validator::make($request->all(), [
    'avatar' => 'required|image|max:2048',
]);

if ($validator->passes()) {
    $user->save();
}

Styles are the core abstraction. The style string is WIDTHxHEIGHT plus an optional modifier — # crops, > shrinks-only, < enlarges-only, and no modifier forces exact dimensions. Define every size the UI needs up front; adding a style later requires reprocessing existing records (see below).

Multiple attachments per model. Just add more keys to $stapler. Each key becomes an Attachment object accessible as $model->{name}, so $model->avatar->url() and $model->resume->originalFilename() coexist without collision.

Accessing attachment metadata. The Attachment object exposes ->url($style), ->path($style), ->originalFilename(), ->contentType(), ->size(). Use these in views and Blade partials instead of rebuilding paths by hand.

Custom interpolation. The default path template (:attachment/:id_partition/:style/:filename) is configurable per attachment via 'path' and 'url'. Tokens include :attachment, :id, :id_partition, :style, :filename, :extension, :hash. Change the template if you need a deterministic, non-ID-based layout (e.g. UUIDs or content hashes for CDN friendliness).

Storage drivers. local, s3, and filesystem ship in the box. S3 needs league/flysystem-aws-s3-v3 at the version pinned by the package (see gotchas) and a storageConfig block:

'storage' => 's3',
'storageConfig' => [
    's3' => [
        'key'    => env('AWS_KEY'),
        'secret' => env('AWS_SECRET'),
        'region' => env('AWS_REGION'),
        'bucket' => env('AWS_BUCKET'),
    ],
],

Reprocessing existing files. When you change styles or the path template, iterate the affected table and call the model's reprocess method per attachment:

User::chunk(100, function ($users) {
    foreach ($users as $user) {
        $user->avatar->reprocess();
    }
});

Testing. In tests, point storageConfig.local.path at a temp directory and swap in a fake request file (or UploadedFile::fake() on Laravel 5+). Because the actual writes happen on save(), assertions are usually just assertFileExists($attachment->path('thumb')).

Gotchas and Tips

This package is effectively abandoned. The last meaningful commits are from the Laravel 5 era; it depends on Flysystem 1.x and an old Imagine release. On PHP 8+ you will hit deprecations and signature mismatches in Imagine. If you must run it, pin PHP 7.4 — or budget time to fork and patch.

Never put the upload attribute in $fillable. Stapler's setAttribute intercepts raw strings and will treat them as file paths. Always assign via Stapler::getFile() (or $model->avatar = $uploadedFile; with an actual UploadedFile object). Mass-assigning $request->all() into a model with avatar fillable is the single most common source of "why is my file empty" bug reports.

save() is required even if nothing else changed. Assign-and-forget silently discards the upload. Conversely, $model->avatar = null followed by save() is the supported delete path — direct unlink on disk leaves the model's stored metadata out of sync.

Old files are not always cleaned up. Replacing an attachment deletes the previous file only when the styles and path template are identical. If you change the path interpolation mid-life of a model, orphaned files accumulate. Add an occasional sweep job against the storage directory, or delete model-triggered files explicitly with an deleting observer.

Style order matters for quality, not correctness. The original file is always retained untouched; each style is generated independently from the original, not chained. So 'large' => '800x800>', 'thumb' => '100x100#' is safe — but the largest style determines upload bandwidth and disk usage, so pick your maximum deliberately.

# vs > is a real footgun. 100x100# will upscale small images to crop; 100x100> will skip resizing entirely if the image is already smaller, leaving the thumb larger than expected. Combine with a required|image|dimensions:min_width=100 validation rule if layout consistency matters.

Storage driver mismatches when swapping S3 in. The bundled Flysystem adapter version is ancient. If Composer pulls a newer aws/aws-sdk-php, you'll get runtime Class not found errors. Pin both sides (league/flysystem: ^1.0, league/flysystem-aws-s3-v3: ~1.0) or vendor the adapter.

Validation is a plain Laravel concern. Don't expect Stapler to validate file type or size — use your framework's image|mimes|max: rules before assigning, and check $file->isValid() yourself if you're bypassing the framework's upload handling.

Extension points are the way to survive its age. If you need custom URLs (signed S3 URLs, CDN prefixes), subclass Attachment and override url() via the 'class' key in $stapler. If you need custom resizing logic, override generateStyle() and use Imagine directly — it's the same underlying library you'd reach for anyway.

Debugging checklist when a file "isn't uploading": (1) is the attribute in $fillable? remove it; (2) did you call save()?; (3) does storageConfig.local.path exist and is it writable?; (4) is the input name identical to the $stapler key? Stapler::getFile('avatar') must match $stapler['avatar']; (5) check storage/logs/laravel.log — Imagine's exceptions surface there, not in the HTTP response.

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.
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core