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

Filament Link Picker Laravel Package

codedor/filament-link-picker

A Filament form field that lets editors choose links without hand-typing URLs. It provides a searchable picker for internal pages, model records, and custom routes, with configurable sources and labels. Use it for navigation, buttons, and CTA fields to keep links consistent in Laravel admin panels.

View on GitHub
Deep Wiki
Context7

Getting Started

v2.2.0 is the first release in this assessment, so treat the notes as "what this package looks like today" rather than a diff from a known baseline.

composer require wotzebra/filament-link-picker
  1. Read the README's three sections first: the LinkPicker form component, the Link value object, and the cast. Everything else in the package hangs off those three.
  2. Pick one model attribute that stores a link (e.g. pages.link, posts.cta) and make the column json/text. The picker round-trips a structured payload, not a plain string.
  3. Wire the cast on the model so the attribute hydrates as a Link value object:
protected function casts(): array
{
    return [
        'link' => \Wotzebra\FilamentLinkPicker\Casts\LinkCast::class,
    ];
}
  1. Drop it into a Filament form:
use Wotzebra\FilamentLinkPicker\Forms\Components\LinkPicker;

LinkPicker::make('link')
    ->label('Call to action');
  1. Render it in Blade through the value object, not raw JSON.

First realistic use case: a "Read more" link on a Page resource that may point at an internal route (with an anchor) or an external URL, with the external variant opening in a new tab. That single case exercises the internal/external branch, the anchor handling fixed in this release, and the new defaultNewTab behaviour.

Implementation Patterns

Value-object rendering. Keep URL assembly in one place — a Blade component or an accessor — so target/rel logic isn't scattered:

@if ($page->link)
    <a href="{{ $page->link->getUrl() }}"
       @if ($page->link->opensInNewTab()) target="_blank" rel="noopener noreferrer" @endif>
        {{ $page->link->getLabel() ?? 'Read more' }}
    </a>
@endif

The exact accessor name for the new-tab flag lives on the Link class — v2.2.0 adds defaultNewTab, so check the class for opensInNewTab() vs shouldOpenInNewTab() before shipping.

Let external links default to new tab. PR #48 enables defaultNewTab for external links. This is the pattern the package now nudges you toward: stop hardcoding target="_blank" on every outbound link in Blade and let the stored link carry the intent. Only override when an editor explicitly unchecks it.

Internal route + anchor deep links. PR #34 fixes anchors being dropped from route-based links. The natural workflow now is:

  • editor picks an internal route from the searchable route list,
  • editor adds a label,
  • anchor fragment survives into the resolved URL (/blog/post#comments).

Use this for in-page table-of-contents links, FAQ jump links, and "jump to section" CTAs. Previously you'd have concatenated # manually in an accessor — delete those shims.

Repeater/Builder blocks. The picker works well inside Repeater and Builder schemas for footers, CTA grids, and navigation items. Store the whole link payload per block; avoid splitting url, label, target into separate top-level columns, or you'll fight the cast on every save.

Form-to-model flow. When using the field outside a Resource (custom Livewire form, action modal), pass the value object in via ->default(fn () => $record->link) and let the cast handle the save. Don't call ->formatStateUsing() with json_decode — that bypasses the cast and produces inconsistent state between create and edit screens.

Testing. Assert the persisted JSON shape once (internal vs external fixtures) and then assert rendered output. That keeps the brittle part contained when the package's payload gains keys like defaultNewTab.

Gotchas and Tips

  • defaultNewTab is a silent behaviour change. After upgrading from 2.1.0, existing external links may start rendering with target="_blank" where they previously didn't, if your Blade layer was already ignoring the stored target. Audit any template that hardcodes target — you can end up with a doubled attribute or a conflicting _self.
  • Clean up anchor workarounds. If you were appending '#'.$fragment in an accessor to compensate for PR #34, remove it now or you'll render #section#section.
  • rel="noopener noreferrer" is not automatic. The package controls target; you control rel. Keep it in your render layer for every _blank link.
  • Old rows lack the new key. Records saved under 2.1.0 won't have defaultNewTab in their JSON. Make your template null-safe (?? false) rather than assuming the property exists on every hydrated Link.
  • Route cache. Internal links are stored as resolved routes/params. After adding or renaming routes, php artisan route:clear (or route:cache in deploy) before editors open the picker, or the route search will show stale/broken entries. Deleting a route silently breaks every stored link pointing at it — there's no referential integrity here.
  • Nullable state. Decide early whether the attribute is nullable and configure the field accordingly. A Link that hydrated from null and one that hydrated from an empty array behave differently when you call getUrl().
  • Serialisation on save. If you see Array to string conversion or the cast silently nulling on save, it's almost always a $fillable/$casts mismatch or a manual $model->link = $request->input('link') bypassing the form's state.
  • External URL validation. Restrict schemes (http, https, mailto, tel) in the field or a FormRequest; the picker is a UI, not a URL sanitiser.
  • The dependabot entries are CI-only. The actions/checkout 6→7 and fetch-metadata bumps don't affect runtime — nothing to do on your side.
  • Extension points are thin by design. For custom behaviour (label defaults, extra link types, resolution logic for a CMS-managed route list), prefer a small wrapper Blade component or a custom field that composes LinkPicker over forking the package; the value object is the stable seam, so code against it rather than against the field's internal state.
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.
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect
youbar/easy-crud
c-point-labs/laravel-hostyourai
dbravoan/dbcms-admin-theme-dbcms
dbravoan/dbcms-theme-db-cyber
dbravoan/dbcms-blocks-basic
dbravoan/dbcms-module-content-audit
dbravoan/dbcms-module-ai-assistant
paygate-to/paygate-php
padosoft/askmydocs-connector-base
defstudio/supercharged-carbon
bernskioldmedia/laravel-matomo