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
attachment-field
van-ons/filament-attachment-library
claude-code
laravel
models
eloquent
attachments
filament

Add a Filament Attachment Library AttachmentField to a Filament form (resource, page, or relation manager). Use when the user wants to attach files/images/PDFs/videos to a model through a Filament form — picking from the attachment library rather than uploading directly via Filament's FileUpload. Covers column-backed vs relationship-backed storage, MIME filtering, multi-select, reordering, drag & drop uploads, focal points, and front-end display.

Install
php artisan boost:add-skill van-ons/filament-attachment-library

Save this content to: .claude/skills/attachment-field/SKILL.md

# Add an AttachmentField to a Filament form

## When to use this skill

Use this when the user asks to:

- Add an attachment / image / file / gallery field to a Filament resource, page, or relation manager.
- Let users pick from the attachment library instead of uploading inline.
- Show a focal-point picker for a stored image.
- Display an attachment on the front end (Glide-backed responsive image).

Do **not** use this skill for:

- The initial package install (the `filament-attachment-library:install` command and panel-plugin registration are covered by the always-loaded guidelines).
- Custom file processing (`AttachmentManager` / `Glide` workflows belong to the upstream `laravel-attachment-library`).

## Step 1 — Pick a storage mode

| You want… | Mode | What to do |
|---|---|---|
| One attachment ID stored on the model itself | **Column** | Add a column (e.g. `featured_image_id`) on the model's table. No trait needed. |
| Multiple attachments, or attachments grouped by purpose ("gallery", "downloads", …) | **Relationship** | Add the `HasAttachments` trait. The pivot's `collection` column distinguishes groups. |

Both modes can be mixed on the same model.

## Step 2 — Prepare the model

### Column mode

Make sure the column exists and is fillable. The field stores the attachment's primary key:

```php
// migration
$table->foreignId('featured_image_id')->nullable()->constrained('attachments')->nullOnDelete();
```

```php
// App\Models\Article
protected $fillable = ['featured_image_id', /* … */];
protected $casts = ['featured_image_id' => 'integer'];
```

### Relationship mode

Add the trait. The migrations from the upstream package already create `attachments` and `attachables` (with `collection` and `order` columns):

```php
namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphToMany;
use VanOns\LaravelAttachmentLibrary\Concerns\HasAttachments;

class Article extends Model
{
    use HasAttachments;

    public function gallery(): MorphToMany
    {
        return $this->attachmentCollection('gallery');
    }
}
```

The `gallery()` accessor is optional but recommended — it lets the rest of the app read the right slice without re-specifying the collection name.

## Step 3 — Add the field to the form

```php
use VanOns\FilamentAttachmentLibrary\Forms\Components\AttachmentField;

public static function form(Form $form): Form
{
    return $form->schema([
        // Column-backed
        AttachmentField::make('featured_image_id')
            ->image()
            ->required(),

// Relationship-backed gallery
        AttachmentField::make('gallery')
            ->relationship()           // uses the `attachments` morph relation
            ->collection('gallery')    // pivot tag; defaults to field name
            ->multiple()
            ->reorderable()
            ->image()
            ->maxFiles(20),
    ]);
}
```

### Field options

| Method | Default | Notes |
|---|---|---|
| `relationship(string = 'attachments')` | column mode | Switches to relationship mode and disables dehydration. |
| `collection(?string)` | field name (when `relationship()` is called) | Pivot `collection` value. Set this if you want the field name and collection to differ. |
| `multiple(bool\|Closure = true)` | `false` | Multi-select. |
| `layout(AttachmentFieldLayout\|Closure)` | `AttachmentFieldLayout::GRID` | How selected items are shown: `GRID` cards, `LIST` rows, or `INPUT` rows that look like a text input (thumbnail or icon, name, type badge; click to pick, no separate button). |
| `compact(bool\|Closure = true)` | `false` | Shortcut for `layout(AttachmentFieldLayout::LIST)`. |
| `reorderable(bool\|Closure = true)` | `true` (only effective with `multiple()`) | Drag-and-drop ordering. Persists to the pivot's `order` column. |
| `mime(string)` | none | MIME filter for the picker. Wildcards allowed (`'image/*'`). |
| `image()` / `video()` / `audio()` / `text()` | — | Shortcut for `mime('image/*')` etc. |
| `extensions(array)` | none | Extension filter for the picker and uploads, e.g. `['vtt', 'srt']`. Case-insensitive. |
| `minFiles(int)` / `maxFiles(int)` | unbounded | Wrappers for `minItems` / `maxItems` from `CanLimitItemsLength`. |
| Standard Filament rules (`required`, `nullable`, …) | — | Work as usual. See [Filament validation docs](https://filamentphp.com/docs/forms/validation). |

### Decision: column or relationship?

```
Single attachment, lives only on this model? ─ column
Multiple attachments?                        ─ relationship + multiple()
Need to reuse the same attachment across models? ─ relationship (column would duplicate IDs)
Need ordering? ─ relationship + multiple() + reorderable()
Compact rows instead of grid cards? ─ compact()
Small, input-like field (forms, table repeaters)? ─ layout(AttachmentFieldLayout::INPUT)
```

## Step 4 — Display attachments

Use the Blade component shipped by the upstream package — never hand-roll URLs (Glide URLs are signed):

```blade
{{-- Column-backed: $article->featured_image_id is an int --}}
<x-laravel-attachment-library-image
    :src="$article->featured_image_id"
    size="full"
    aspect-ratio="16/9"
/>

{{-- Relationship-backed: iterate the relation --}}
@foreach ($article->gallery as $image)
    <x-laravel-attachment-library-image :src="$image" size="medium" />
@endforeach
```

The component accepts an `Attachment` instance, an integer ID, or a string filename.

## Step 5 (optional) — Focal point picker

If a column-backed image needs a focal point, use the upstream `focal_point` JSON column and the `FocalPointPicker` field:

```php
use VanOns\FilamentAttachmentLibrary\Filament\Fields\FocalPointPicker;

FocalPointPicker::make('focal_point')
    ->image(fn ($get) => $get('featured_image_id'));
```

State shape: `['x' => 0–100, 'y' => 0–100]` (percent). Persist to a `json` column on the model. The `Attachment` model already has a `focal_point` json cast — use that if you're storing the focal point on the attachment itself rather than per-usage.

## Drag & drop uploads

Built in — no configuration, no extra components. Users can drop files directly onto the field (uploaded to the library base path and selected) or onto the library page/browser modal (uploaded to the directory currently open). The field's constraints apply to drops automatically:

- `mime()` / `image()` etc. — checked client-side and re-validated server-side on the detected mime type.
- `multiple()` / `maxFiles()` — a single field takes one file from a multi-drop; multi fields stop at the cap.
- File size — bounded by Livewire's temporary upload limit (`livewire.temporary_file_upload.rules`).
- `config('filament-attachment-library.upload_rules')` — applies to dropped files too.

Rejected files produce per-file danger notifications; nothing else to wire up.

## Common follow-ups

- **Filtering attachments by MIME on the picker**: `->mime('image/png')` or one of the shortcuts. The picker also exposes a built-in MIME filter UI; `->mime()` *restricts* the picker further.
- **Custom upload validation**: add rules to `config/filament-attachment-library.php` `upload_rules`, not on the field. The library page uploads files outside the form's validation context — the same rules also run for drag & drop uploads.
- **Raising the drop upload size limit**: configure `livewire.temporary_file_upload.rules` (and your PHP `upload_max_filesize`/`post_max_size`), not the field.
- **Per-tenant base path**: `FilamentAttachmentLibrary::make()->basePath(fn () => 'tenants/' . tenant()->slug)` on the panel — see the always-loaded guidelines.
- **Custom user display in the browser**: set `user_model` and `username_property` in `config/filament-attachment-library.php`.

## Anti-patterns

- ❌ Using Filament's `FileUpload` to store attachments — bypasses the library, no `attachments` rows are created.
- ❌ Adding a custom dropzone (or `FileUpload`) to get drag & drop — the field and the browser already accept dropped files.
- ❌ Calling `relationship()` without the `HasAttachments` trait on the model — the field can't load existing state.
- ❌ Using `reorderable()` without `multiple()` — has no effect; reordering only applies when there are multiple selected items.
- ❌ Setting the `collection` to a value that overlaps with another field's collection on the same model — they will share the same pivot rows.
- ❌ Hand-constructing `/storage/...` or Glide URLs — go through `<x-laravel-attachment-library-image>` or `$attachment->url`.
- ❌ Assuming `getState()` returns IDs in the original order without `reorderable()` — without the `order` pivot column being maintained, ordering is not stable.

Add an AttachmentField to a Filament form

When to use this skill

Use this when the user asks to:

  • Add an attachment / image / file / gallery field to a Filament resource, page, or relation manager.
  • Let users pick from the attachment library instead of uploading inline.
  • Show a focal-point picker for a stored image.
  • Display an attachment on the front end (Glide-backed responsive image).

Do not use this skill for:

  • The initial package install (the filament-attachment-library:install command and panel-plugin registration are covered by the always-loaded guidelines).
  • Custom file processing (AttachmentManager / Glide workflows belong to the upstream laravel-attachment-library).

Step 1 — Pick a storage mode

You want… Mode What to do
One attachment ID stored on the model itself Column Add a column (e.g. featured_image_id) on the model's table. No trait needed.
Multiple attachments, or attachments grouped by purpose ("gallery", "downloads", …) Relationship Add the HasAttachments trait. The pivot's collection column distinguishes groups.

Both modes can be mixed on the same model.

Step 2 — Prepare the model

Column mode

Make sure the column exists and is fillable. The field stores the attachment's primary key:

// migration
$table->foreignId('featured_image_id')->nullable()->constrained('attachments')->nullOnDelete();
// App\Models\Article
protected $fillable = ['featured_image_id', /* … */];
protected $casts = ['featured_image_id' => 'integer'];

Relationship mode

Add the trait. The migrations from the upstream package already create attachments and attachables (with collection and order columns):

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphToMany;
use VanOns\LaravelAttachmentLibrary\Concerns\HasAttachments;

class Article extends Model
{
    use HasAttachments;

    public function gallery(): MorphToMany
    {
        return $this->attachmentCollection('gallery');
    }
}

The gallery() accessor is optional but recommended — it lets the rest of the app read the right slice without re-specifying the collection name.

Step 3 — Add the field to the form

use VanOns\FilamentAttachmentLibrary\Forms\Components\AttachmentField;

public static function form(Form $form): Form
{
    return $form->schema([
        // Column-backed
        AttachmentField::make('featured_image_id')
            ->image()
            ->required(),

// Relationship-backed gallery
        AttachmentField::make('gallery')
            ->relationship()           // uses the `attachments` morph relation
            ->collection('gallery')    // pivot tag; defaults to field name
            ->multiple()
            ->reorderable()
            ->image()
            ->maxFiles(20),
    ]);
}

Field options

Method Default Notes
relationship(string = 'attachments') column mode Switches to relationship mode and disables dehydration.
collection(?string) field name (when relationship() is called) Pivot collection value. Set this if you want the field name and collection to differ.
multiple(bool|Closure = true) false Multi-select.
layout(AttachmentFieldLayout|Closure) AttachmentFieldLayout::GRID How selected items are shown: GRID cards, LIST rows, or INPUT rows that look like a text input (thumbnail or icon, name, type badge; click to pick, no separate button).
compact(bool|Closure = true) false Shortcut for layout(AttachmentFieldLayout::LIST).
reorderable(bool|Closure = true) true (only effective with multiple()) Drag-and-drop ordering. Persists to the pivot's order column.
mime(string) none MIME filter for the picker. Wildcards allowed ('image/*').
image() / video() / audio() / text() — Shortcut for mime('image/*') etc.
extensions(array) none Extension filter for the picker and uploads, e.g. ['vtt', 'srt']. Case-insensitive.
minFiles(int) / maxFiles(int) unbounded Wrappers for minItems / maxItems from CanLimitItemsLength.
Standard Filament rules (required, nullable, …) — Work as usual. See Filament validation docs.

Decision: column or relationship?

Single attachment, lives only on this model? ─ column
Multiple attachments?                        ─ relationship + multiple()
Need to reuse the same attachment across models? ─ relationship (column would duplicate IDs)
Need ordering? ─ relationship + multiple() + reorderable()
Compact rows instead of grid cards? ─ compact()
Small, input-like field (forms, table repeaters)? ─ layout(AttachmentFieldLayout::INPUT)

Step 4 — Display attachments

Use the Blade component shipped by the upstream package — never hand-roll URLs (Glide URLs are signed):

{{-- Column-backed: $article->featured_image_id is an int --}}
<x-laravel-attachment-library-image
    :src="$article->featured_image_id"
    size="full"
    aspect-ratio="16/9"
/>

{{-- Relationship-backed: iterate the relation --}}
@foreach ($article->gallery as $image)
    <x-laravel-attachment-library-image :src="$image" size="medium" />
@endforeach

The component accepts an Attachment instance, an integer ID, or a string filename.

Step 5 (optional) — Focal point picker

If a column-backed image needs a focal point, use the upstream focal_point JSON column and the FocalPointPicker field:

use VanOns\FilamentAttachmentLibrary\Filament\Fields\FocalPointPicker;

FocalPointPicker::make('focal_point')
    ->image(fn ($get) => $get('featured_image_id'));

State shape: ['x' => 0–100, 'y' => 0–100] (percent). Persist to a json column on the model. The Attachment model already has a focal_point json cast — use that if you're storing the focal point on the attachment itself rather than per-usage.

Drag & drop uploads

Built in — no configuration, no extra components. Users can drop files directly onto the field (uploaded to the library base path and selected) or onto the library page/browser modal (uploaded to the directory currently open). The field's constraints apply to drops automatically:

  • mime() / image() etc. — checked client-side and re-validated server-side on the detected mime type.
  • multiple() / maxFiles() — a single field takes one file from a multi-drop; multi fields stop at the cap.
  • File size — bounded by Livewire's temporary upload limit (livewire.temporary_file_upload.rules).
  • config('filament-attachment-library.upload_rules') — applies to dropped files too.

Rejected files produce per-file danger notifications; nothing else to wire up.

Common follow-ups

  • Filtering attachments by MIME on the picker: ->mime('image/png') or one of the shortcuts. The picker also exposes a built-in MIME filter UI; ->mime() restricts the picker further.
  • Custom upload validation: add rules to config/filament-attachment-library.php upload_rules, not on the field. The library page uploads files outside the form's validation context — the same rules also run for drag & drop uploads.
  • Raising the drop upload size limit: configure livewire.temporary_file_upload.rules (and your PHP upload_max_filesize/post_max_size), not the field.
  • Per-tenant base path: FilamentAttachmentLibrary::make()->basePath(fn () => 'tenants/' . tenant()->slug) on the panel — see the always-loaded guidelines.
  • Custom user display in the browser: set user_model and username_property in config/filament-attachment-library.php.

Anti-patterns

  • ❌ Using Filament's FileUpload to store attachments — bypasses the library, no attachments rows are created.
  • ❌ Adding a custom dropzone (or FileUpload) to get drag & drop — the field and the browser already accept dropped files.
  • ❌ Calling relationship() without the HasAttachments trait on the model — the field can't load existing state.
  • ❌ Using reorderable() without multiple() — has no effect; reordering only applies when there are multiple selected items.
  • ❌ Setting the collection to a value that overlaps with another field's collection on the same model — they will share the same pivot rows.
  • ❌ Hand-constructing /storage/... or Glide URLs — go through <x-laravel-attachment-library-image> or $attachment->url.
  • ❌ Assuming getState() returns IDs in the original order without reorderable() — without the order pivot column being maintained, ordering is not stable.
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.
hsluv/hsluv
seampact/codegen
seampact/hooks
seampact/ir
mehedimi/laravel-amp-mail
happones/laravel-vue-i18n-generator
jwerd/inertia-table
siberfx/flysystem-wasabi
hollyit/token-replace
ippanelcom/laravel-sdk
adachsoft/changelog-linter
adachsoft/collection
payerurl/telegrambot-crypto-payment-instant-payout
dskripchenko/laravel-hybrid-search
riorizkyrainey/lara-ced
mehedimi/static-vel
zharp/zharp
nonsapiens/bigquery-model-sync
lgu-bayambang-ict-office/lgu-laravel-development-standards
andydefer/laravel-support