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

Laravel Stapler Laravel Package

codesleeve/laravel-stapler

Laravel Stapler is an easy file-attachment package for Laravel that handles uploads, storage, and image processing with a simple configuration. It supports local and S3 storage, automatic resizing and thumbnails, and clean model integration.

View on GitHub
Deep Wiki
Context7

Getting Started

Stapler binds file uploads to Eloquent models Paperclip-style: you declare an attachment, and the package handles write, transform, URL, and delete. The mental model is "the file is a column on the row," so start by thinking in those terms.

Install and wire up

Check the composer constraint resolves against your framework first — this package targets the Laravel 4.2–5.x era and will not install on Laravel 9+ without a fork.

composer require codesleeve/laravel-stapler

Register the provider explicitly (auto-discovery is unreliable here):

// config/app.php
Codesleeve\LaravelStapler\LaravelStaplerServiceProvider::class,

Publish config and read it before writing model code — this is your first stop:

php artisan vendor:publish --provider="Codesleeve\LaravelStapler\LaravelStaplerServiceProvider"

config/stapler.php holds default storage (filesystem vs s3), path/URL interpolations, and the Imagine driver (gd/imagick). Every quirk you'll hit later traces back to this file.

First use case — a single avatar

The smallest working slice: a User with one avatar, two styles.

use Codesleeve\Stapler\ORM\StaplerableInterface;
use Codesleeve\Stapler\ORM\EloquentTrait;

class User extends Eloquent implements StaplerableInterface
{
    use EloquentTrait;

    protected $fillable = ['avatar'];

    public function __construct(array $attributes = [])
    {
        $this->hasAttachedFile('avatar', [
            'styles' => [
                'thumb'  => '64x64#',
                'medium' => '256x256',
            ],
        ]);
        parent::__construct($attributes);
    }
}

Migration adds the fields Stapler expects:

$table->string('avatar_file_name');
$table->string('avatar_content_type');
$table->integer('avatar_file_size');
$table->timestamp('avatar_updated_at');

Assign via the request, save the model, read the URL:

$user->avatar = Input::file('avatar'); // Symfony UploadedFile
$user->save();

$user->avatar->url('thumb');    // string URL
$user->avatar->path('medium');  // absolute path on disk

If that round-trips (upload → thumbnail appears on disk → URL resolves), the package is wired. If it doesn't, the failure is almost always storage paths in config/stapler.php or the imagine driver missing — check those before anything else.

Implementation Patterns

Declaring attachments in the constructor

The hasAttachedFile() calls belong in the model constructor (after parent), not in boot(). The trait relies on the properties being set at instantiation. A very common bug is putting them in boot() and wondering why styles never register.

Styles — the core feature

Styles are named derivatives defined inline. The syntax is Imagine geometry with an operator:

'styles' => [
    'original' => '600x600>',   // shrink, preserve ratio
    'thumb'    => '100x100#',   // resize and center-crop
    'large'    => '1024x1024',
],
  • > shrink only, < grow only, ! force, # center-crop.
  • Omit original to keep the untouched upload as the base and generate only derivatives.
  • The original style is always stored; derivative styles are generated synchronously at save time.

Interpolation strings — where files land

Paths and URLs are template strings using model attributes. This is the knob you tune per model:

$this->hasAttachedFile('photo', [
    'url'  => '/system/:attachment/:id_partition/:style/:filename',
    'path' => ':laravel_root/public/system/:attachment/:id_partition/:style/:filename',
    'styles' => [ 'thumb' => '80x80#' ],
]);

:id_partition groups IDs into 000/000/001 subpaths — keep it, because putting thousands of files in one directory degrades on some filesystems and slows listing. :hash avoids filename collisions when users upload same-named files. :style is mandatory for URL templates with multiple styles.

Validation

Stapler does not validate by itself. Validate in the FormRequest or controller before assigning:

$rules = ['avatar' => 'required|image|max:5120|mimes:jpg,jpeg,png,gif'];

Then attach the file. A missing validation layer means arbitrary UploadedFile shapes hit Imagine — treat this as a hard requirement, not optional polish.

Storage: local vs S3

Swap the default in config/stapler.php, or override per-attachment:

'storage' => 's3',
's3_client_config' => [
    'key'    => env('AWS_KEY'),
    'secret' => env('AWS_SECRET'),
    'region' => 'us-east-1',
    'bucket' => env('AWS_BUCKET'),
],

S3 URL templates look different — usually :s3_object_url. Remember: on S3, every byte transits your PHP worker. There is no direct upload.

Automatic cleanup

Deleting the model deletes file(s) via the deleting event, as long as use EloquentTrait is intact on the model. Soft deletes do not trigger cleanup — by design, a soft delete leaves the file on disk. Handle that explicitly in whatever retention flow you have.

Replacing an attachment

Assign a new UploadedFile and save. Stapler deletes the old derivative set and writes new ones. There's no version history — if you need it, that's a custom implementation on top.

Polymorphic attachments

For a shared "attachment" table across models, use Stapler on the child record (one attachment per row) and let Eloquent's polymorphic relations do the rest. Stapler's own multi-attachment support is limited — it's not a media library. If your requirements drift toward galleries or multi-file records, treat that as a signal to evaluate a maintained alternative.

Extension points

  • Custom storage adapters: implement Codesleeve\Stapler\Storage\StorageInterface.
  • Custom file naming: override ::filename interpolation or set 'filename' => function($file) { ... }.
  • Post-processing hooks: bind to model events or extend the config callback if the version you have exposes one.

Wrap any custom logic behind an internal AttachmentService interface. The public API is small enough that a thin service in front of it makes a future replace-stapler migration an afternoon instead of a sprint.

Gotchas and Tips

Version constraints are the whole game. Laravel 9+/PHP 8.1+ will not resolve. If composer require fails, the evaluation is over — don't burn a day fighting constraints, fork and patch, or pivot. This is a go/no-go gate, not a step to work around.

Auto-discovery is unreliable. Always register the provider manually in config/app.php. If you see Class 'Codesleeve\LaravelStapler\...' not found at boot, this is why.

Never trust filenames. Path traversal and accidental overwrites are live risks if interpolation includes the raw client filename. Use :hash or override the filename callback with a sanitizer (basename(), allowlist characters, strip null bytes). The package will happily write wherever you tell it — that's a feature and a footgun.

Synchronous processing is the default. Large uploads → long request times → PHP memory exhaustion. Practical mitigations on the current package: cap upload size aggressively in validation, tune memory_limit and max_execution_time, and consider an out-of-band conversion job that shells into Imagine against the stored original, since Stapler won't queue for you.

Silent partial failures. It's common for one style to fail (missing directory, Imagine decode error) while the rest persist. Nothing throws to the user; the result is broken thumbnails rather than an exception. After a big deploy or config change, spot-check path('thumb') for a few fresh records.

Orphaned files are a real class of bug. Rollback after file write, or delete-record-after-save failure, leaves files on disk. There is no reconciliation job. Build one: nightly diff of storage/app/system directory vs the model table, report or delete orphans beyond a grace window.

Concurrent derivatization races. Two simultaneous saves for the same new model (rare but real) can collide on the same derivative paths. There's no locking. If your domain exposes this, add a lock at the service layer.

Config caching bites on class callbacks. If config/stapler.php uses PHP closures for filenames or interpolations, php artisan config:cache will break them (closures aren't serializable). Use string callbacks ('My\\FileNameResolver::build') or move logic into the model.

Path vs URL mismatch is the #1 debug rabbit hole. path() returns a filesystem path; url() returns the public URL. If a file generates but the image is broken, compare realpath($model->photo->path('thumb')) against where your web server serves $model->photo->url('thumb'). Nine times out of ten the path or url interpolation is wrong, not the file.

S3 legacy client. The bundled adapter uses AWS SDK v2. S3 SigV4, newer regions, and anything requiring modern SDK semantics need a fork. Verify your target region and bucket policy before assuming S3 works out of the box.

Imagick vs GD matters for output quality and edge cases. Set the driver explicitly in config/stapler.php. GD is more available; Imagick handles formats and colors better. Test thumbnail quality with the actual production driver, not the dev one.

No queue, no direct-to-S3, no resumable uploads. If any of those are on the roadmap, choose a maintained alternative now rather than discovering it mid-project. spatie/laravel-medialibrary is the default forward path; a thin Flysystem + Intervention Image service is the fully-owned option.

Pin the version and name an owner. The package is unmaintained, and you inherit its security surface (MIME validation, image decode limits, public-disk exposure). The mitigation is organizational, not technical: pin, isolate behind an internal interface, and put a dated exit plan in the codebase.

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.
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment