
Summary
URL input with a live Open Graph / HTML meta preview card. The field uses the FlexTextInput pill shell for the input track and fetches page metadata through a server-side scrape endpoint (cached, rate-limitable). Preview cards support three layouts, optional URL prefix/suffix affixes, and configurable debounce / skeleton timing.Basic usage
State format
Important: when
prefix('https://') is set, the stored state is always the full URL (https://example.com/path). The visible input shows only the suffix (example.com/path) for readability. On blur, pasted full URLs are normalized back to suffix-only display.
null.
Preview layouts
Three card layouts viapreviewLayout():
role="alert" region below the card.
Configuration API
Each fluent method accepts aClosure for dynamic values (e.g. based on $get, $record, or $livewire).
variant(string|Closure $variant)
Visual style shared with FlexTextInput.
size(string|ControlSize|Closure $size)
Control height. See Control size. Default: md.
preview(bool|Closure $condition = true)
Enable or disable the preview card entirely. When false, the field behaves as a styled URL input only.
previewDebounce(int|Closure $milliseconds)
Delay after typing before the client calls the scrape endpoint. Default: 500. Pass 0 for immediate fetch (use sparingly).
previewMinUrlLength(int|Closure $length)
Minimum resolved URL character length before scraping starts. Default: 10. Enforced minimum: 4.
previewMinSkeletonMs(int|Closure $milliseconds)
Minimum skeleton display time on initial card reveal (SSR-prefilled URLs or first client fetch). Default: 500. Prevents flicker when metadata resolves instantly from cache.
previewLayout('horizontal'|'vertical'|'card'|Closure $layout)
Card layout. Default: horizontal. Invalid values throw InvalidArgumentException.
resolveInitialPreviewOnServer(bool|Closure $condition = true)
When true (default), the Blade view calls resolveInitialPreview() during SSR for prefilled URLs so the card can render immediately without waiting for Alpine.
When false, initial preview is deferred to the client — useful on heavy forms to avoid blocking page render or duplicate scrapes.
showVisitLink(bool|Closure $condition = true)
When true (default), the domain row is an <a> opening the URL in a new tab (rel="noopener noreferrer").
When false, the domain is plain text with the same icon styling (fff-link-preview__domain--text).
visitLabel(string|Closure $label)
Accessible label for the visit link (aria-label). Default: translated filament-flex-fields::default.link_preview.visit.
visitIcon(string|BackedEnum|Htmlable|Closure|null $icon)
Icon beside the domain row. Default: GravityIcon::Paperclip.
prefix(string|Closure|null $label) / suffix(string|Closure|null $label)
Inline affix labels on the FlexTextInput track. Empty strings are treated as no affix.
placeholder(string|Closure|null $placeholder)
Inherited from Filament HasPlaceholder. Default translation: filament-flex-fields::default.link_preview.placeholder.
readOnly(bool|Closure $condition = true) / disabled(bool|Closure $condition = true)
Inherited from Filament. Read-only still shows preview for the current URL; disabled blocks interaction and scraping triggers.
focusOutline(bool|Closure $condition = true)
Inherited from HasFieldFocusOutline. Default: false. When true, shows the shared --fff-field-focus-* ring on the input shell.
Public helper methods
resolveInitialPreview() returns null when preview is disabled, URL is empty, URL is not scrapable, or scrape returns no metadata.
Package configuration
Global scrape behaviour inconfig/filament-flex-fields.php:
Publish config:
.env:
url-meta-scrape.js) so repeated keystrokes do not spam the server.
Scrape endpoint (named route): filament-flex-fields.url-meta.scrape.
Validation
Model & database examples
Recipe: CMS external link block
Recipe: prefixed marketing domain
Recipe: read-only audit display
Recipe: heavy admin form — defer SSR scrape
Recipe: reactive live() — drive sibling fields from preview URL
live() when other form fields should react to URL changes. Preview fetching still debounces independently via previewDebounce().
Recipe: affix-only internal paths (preview disabled)
For intranet or relative paths where server scraping is not useful — URL input only, no preview card:Recipe: scrape errors — longer debounce + no visit link
When users paste slow or rate-limited URLs, reduce churn and keep the domain as plain text:fff-link-preview__error with role="alert"; they do not block form submission when nullable / url rules pass.
Accessibility
- Preview card container uses
aria-live="polite"for loading and reveal updates - When revealed,
aria-labelon the card reflects the scraped page title - Visit link uses
visitLabel()asaria-label - Scrape errors use
role="alert" - Input remains a standard Filament field with label / hint / error association
CSS classes
Shares FlexTextInput shell classes (
fff-flex-text-input, fff-flex-text-input__shell, variant modifiers).
Assets
Lazy-loaded stylesheets (viaFlexFieldAssets::stylesheetsFor('link-preview-field')):
flex-text-inputlink-preview-field
link-preview-field (loaded with x-load).
Uses wire:ignore on the Alpine root — prefer changing Livewire state or sibling fields over direct DOM manipulation in tests.
Implementation notes
- Metadata is scraped server-side via
UrlMetaScraper(Open Graph + fallback<title>/<meta name="description">/<meta property="og:image">). - Non-scrapable URLs (invalid scheme, localhost, etc.) skip preview quietly.
- Image preload runs client-side before revealing the card to avoid layout pop-in.
- Invalid
variant()orpreviewLayout()values throwInvalidArgumentExceptionat render time.
Playground
Slug:link-preview-field
/admin/flex-fields-playground/link-preview-field — see Playground.