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 Paperclip Laravel Package

czim/laravel-paperclip

Laravel Paperclip attaches files and images to Eloquent models, handling storage, image resizing, and processing through configurable drivers. It offers a Paperclip-style API with variants, conversions, and cleanup, making uploads and attachment management simple and consistent in Laravel apps.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and publish the config:

composer require czim/laravel-paperclip
php artisan vendor:publish --provider="Czim\Paperclip\Providers\PaperclipServiceProvider"

The published config/paperclip.php controls the default disk, storage path interpolation, variant defaults, and queued processing. Read it before writing any model code — most day-to-day surprises come from that file, not from the trait.

Next, add the metadata columns for each attachment you plan to use:

Schema::table('users', function (Blueprint $table) {
    $table->string('avatar_file_name')->nullable();
    $table->unsignedBigInteger('avatar_file_size')->nullable();
    $table->string('avatar_content_type')->nullable();
    $table->timestamp('avatar_updated_at')->nullable();
});

Then wire the model. The trait requires the AttachableInterface, and attachments must be registered before parent::__construct() so that mass-assigned attributes in the constructor are picked up:

use Czim\Paperclip\Contracts\AttachableInterface;
use Czim\Paperclip\Model\PaperclipTrait;

class User extends Model implements AttachableInterface
{
    use PaperclipTrait;

    protected $fillable = ['avatar'];

    public function __construct(array $attributes = [])
    {
        $this->hasAttachedFile('avatar', [
            'variants' => [
                'thumb' => '100x100',
            ],
        ]);

        parent::__construct($attributes);
    }
}

First real use case: a profile avatar with a thumbnail. In a controller:

$user->avatar = $request->file('avatar');
$user->save();

$user->avatar->url();        // original
$user->avatar->url('thumb'); // 100x100 variant

Run php artisan storage:link if you use the public disk, otherwise url() will return paths you cannot serve.

Implementation Patterns

Assign-and-save is the primary workflow. Setting $model->avatar = $file marks the attachment dirty; the actual file write and variant generation happen during save(). That keeps transactions sane and means you rarely touch Attachment methods directly for the write path.

Define variants once, per attachment. Variant definitions live in the hasAttachedFile() call or in the config default. String shorthand ('100x100') covers the common resize case; array form exposes width/height and conversion options when you need control over cropping and quality:

$this->hasAttachedFile('avatar', [
    'variants' => [
        'thumb'  => '100x100',
        'medium' => ['resize' => ['width' => 400, 'height' => 400]],
    ],
]);

Use path interpolation, not hardcoded paths. The default storage path interpolates :attribute, :class, :id/:id_partition, :style, :hash, and :extension. Overriding this per attachment is supported via the storage option. If you need per-tenant or per-environment layout, change it in config rather than subclassing.

Overriding column names. If your schema does not follow the {attribute}_file_name / _file_size / _content_type / _updated_at convention, pass an attributes map to hasAttachedFile(). This is the cleanest way to retrofit the package onto an existing table.

Custom attachment behaviour. Subclass the package attachment and pass it as the attachment option. This is the right extension point for producing CDN URLs, adding S3 signed URLs, or normalising filenames — prefer it to sprinkling conditionals in views.

Queued variant processing. For large images or slow disks, enable queued processing in config and run a worker. The original is written synchronously; variants land later. Poll via $model->avatar->url('thumb') only after checking exists(), or trigger reprocessing explicitly.

Lifecycle hooks. Use model observers or the package's attachment events (check Czim\Paperclip\Events\*) for post-save side effects such as cache busting, search indexing, or notifying a downstream service. Do not put that logic in the controller if attachments can be set from more than one place.

Testing. Storage::fake('public') plus UploadedFile::fake()->image('avatar.jpg') covers most tests. Assert against $model->avatar->path() and Storage::disk('public')->exists(...) rather than absolute URLs.

Gotchas and Tips

  • Constructor ordering bites. If you register hasAttachedFile() after parent::__construct(), fillable assignments in the constructor will silently skip the attachment. Also ensure avatar is in $fillable if you mass-assign.
  • Nullable columns are mandatory. Models created without an attachment will otherwise fail on insert. Use unsignedBigInteger for file_size; integer overflows on large uploads.
  • Path interpolation and new models. :id and :id_partition are only resolvable after the insert. The package handles this during save(), but any code that manually calls ->path() or ->url() on an unsaved model will get empty or wrong interpolations.
  • Replacing and deleting files. By default, assigning a new file removes the old one. If you need history or soft-delete semantics, disable removal in config and clean up yourself; otherwise you will lose originals unexpectedly. Deleting the model does not necessarily remove the files — handle that in a deleting observer.
  • Image processing needs an Imagine driver. Variant generation depends on GD or Imagick being installed and configured. Silent variant failures usually mean a missing extension or a memory limit; check logs and temporarily switch to synchronous processing to see the exception.
  • Variant failures under queue. With queued processing, exceptions are easy to miss. Ensure failed jobs are monitored, and provide a backfill command that calls the attachment's reprocess method for records with missing variants.
  • Config caching and closures. Closures inside config/paperclip.php will break config:cache. Keep the config file plain arrays; closures belong in model code or a service provider.
  • url() is disk-driven. It reflects the disk's url setting. For S3 or a CDN, configure the disk and, if you need signed or transformed URLs, override the attachment class rather than post-processing strings in Blade.
  • Validation is your job. The package stores whatever content_type the client sent. Validate MIME types and size with Laravel's mimes/max rules before assignment; do not trust contentType() for security decisions.
  • Memory and size limits. Resizing large images is the most common source of timeouts. Cap upload size at the validation layer, and consider skipping variants for non-image content types.
  • Debugging tip. When an attachment appears set but url() is empty, inspect the four metadata columns directly. A populated _file_name with a null _updated_at usually means a save path was interrupted before the file write completed.
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.
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console