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

Nova Paperclip Laravel Package

daniel-de-wit/nova-paperclip

View on GitHub
Deep Wiki
Context7

Getting Started

Nova Paperclip gives a Nova resource a drag‑and‑drop file attachment field backed by a polymorphic attachments table, so the same field can be dropped onto any number of models without schema changes per model.

  1. composer require daniel-de-wit/nova-paperclip — 2.3.0 requires Laravel 10/11 and a Nova version that supports Laravel 11 (Nova 4.32+). If you are still on Nova 3, stay pinned to 2.2.x.
  2. Publish and run the migration that creates the attachments table: php artisan vendor:publish --tag=paperclip-migrations then php artisan migrate. Nothing renders in Nova until this table exists.
  3. Publish the config (config/paperclip.php or similarly tagged) and set the default disk, directory, and any file‑size/type limits. php artisan storage:link if you use the public disk.
  4. Add the package trait to each model that can own attachments, then reference the field from the Nova resource:
class Invoice extends Model
{
    use HasPaperclip; // trait name per README
}

// app/Nova/Invoice.php
public function fields(Request $request)
{
    return [
        Paperclip::make('Files')
            ->disk('s3')
            ->accept(['application/pdf', 'image/*']),
    ];
}

First real use case: attach a signed PDF or scanned receipt to an order, view/download it from the Nova detail page, and let support staff replace it without a deploy. Validate it end‑to‑end on staging before rolling to multiple models — the polymorphic relation is the part most likely to surprise you.

Implementation Patterns

One field, many models. Because attachments are polymorphic, the usual pattern is a shared trait plus a small Paperclip::make('Files') field on every resource that needs it. Keep the disk/path configuration in the config file rather than per‑field so a future move from local to S3 is a one‑line change.

Naming the field per use case. When a model has more than one attachment slot (e.g. contract and appendices), use the field/collection name to segregate them rather than relying on MIME filters. This keeps queries in your app code ($invoice->getMedia('contract')-style accessors) readable.

Disk strategy. Use public for anything shown to end users via Nova previews; use a private disk plus signed/temporary URLs for PII, contracts, and ID documents. Set visibility explicitly — the default in a lot of setups is inheriting the disk default, which is not always what you want.

Authorization. Wrap the field in canSee/readonly closures so a support role can download but not replace, and only the owning team can upload:

Paperclip::make('Files')
    ->canSee(fn ($request) => $request->user()->can('viewFiles', $this->resource))
    ->readonly(fn ($request) => ! $request->user()->can('uploadFiles', $this->resource)),

Server‑side usage. The trait is the integration point for the rest of the app: generate download URLs in Blade/mail, attach files from a queued job (e.g. a generated report), and copy attachments when duplicating a model. Treat the trait's attach/detach API as the only supported mutation path — writing rows into attachments directly bypasses any path/disk normalization.

Testing. Storage::fake('local') and assert the attachment row plus the file presence; this is much faster than hitting the Nova upload endpoint, and you only need one Nova Dusk/browser test for the drag‑and‑drop UI itself.

Upgrade path. For 2.3.0, run composer update daniel-de-wit/nova-paperclip -W and then composer why-not daniel-de-wit/nova-paperclip 2.3.0 if it refuses — the blocker is almost always a Nova or framework constraint elsewhere, not this package. Re‑run the migration check on a fresh clone to confirm nothing new shipped.

Gotchas and Tips

  • Silent empty field is nearly always a missing migration, an unregistered trait, or a cached config. Run php artisan config:clear and confirm the attachments table exists before debugging anything else.
  • Model events vs bulk deletes. Cleanup hooks only fire on model deletes. Model::where(...)->delete() or a raw query leaves orphaned files on disk and orphan rows in attachments. Use a scheduled prune command that reconciles the table against storage.
  • Upload limits are three layers deep. Nova/Livewire, PHP (upload_max_filesize, post_max_size), and your web server (client_max_body_size in nginx) all need to agree, or large files fail with a generic validation error that looks like a package bug.
  • Storage link and disk visibility. Files on the public disk without php artisan storage:link produce 404s in the Nova preview while the upload itself appears successful.
  • Config cache after disk changes. Switching to S3 in config but forgetting to clear the cached config is a classic "why is it still writing locally?" moment.
  • Filename sanitization. Original client filenames are user input; if you render them back in the UI or use them in download headers, escape/validate them. Prefer the package's generated storage path and keep the original name as metadata only.
  • Permissions on the attachments table. Unlike per‑model columns, everything lives in one table — a misconfigured Nova policy can expose another model's files if you resolve attachments by ID in a custom controller. Always scope lookups through the owning model's relation.
  • Extension point: the field's disk/path callbacks and the config file are the intended seams. Avoid subclassing the field to change storage behaviour; you will fight it at the next minor bump.
  • Pin thoughtfully. 2.3.0 is a constraint‑widening release for Laravel 11 support — low risk, but if your CI matrix still runs Laravel 10, verify both legs rather than assuming the widened constraint is tested upstream.
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.
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
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