pdewit/nova-external-url
Laravel Nova field for adding clickable external URL links to resources. Display a link or button in index and detail views, point it at any URL, and optionally open it in a new tab. Easy to configure in Nova resource classes for quick access to related external pages.
Architecture fit. This is a presentation-layer micro-component for Laravel Nova: one PHP field class plus one Vue component that renders a stored or derived URL as a clickable anchor in admin screens. It has no place in domain logic, service layers, APIs, or shared infrastructure, and no value outside an existing Nova admin panel. The correct framing for planning purposes is "admin UI snippet," and adoption should be scoped to one or a handful of resources — not established as a platform convention other teams copy.
Integration feasibility. Mechanically trivial, contractually uncertain. Installation is composer require → publish provider assets → php artisan nova:publish → drop the field into fields(). The blocker is version alignment, not effort: last release November 2018, 30 stars, zero dependents, no visible CI, and it predates Nova 3, 4, and 5. The compiled Vue assets almost certainly target the Vue 2 toolchain Nova 1.x/2.x shipped, so on Nova 4/5 (Vite + Vue 3) the field will likely fail to register or render. Nova 4+ ships a first-class Laravel\Nova\Fields\URL that produces the same user-visible result at zero dependency cost.
Documentation drift is itself a finding. The package description advertises configurable external links in Nova's sidebar; the actual implementation appears to be a field. Those are different integration points (resource fields() vs. NovaServiceProvider::menu()). Do not let anyone scope work off the README — the source is the spec.
Technical risk register:
rel="noopener noreferrer" alongside target="_blank" — modest reverse-tabnabbing exposure, one-line fix, but a signal of broader neglect.asHtml() puts escaping ownership on us.resolveUsing fields do not sort or filter on the computed value, and inner <a> tags on clickable Nova index rows can hijack row navigation to the detail page unless the component stops propagation.nova-components/ is permissible — check before planning around it.Key questions to resolve before committing:
laravel/nova constraint actually include it?src/*.php extend?Stack fit. Valid only for Laravel + Nova 1.x/2.x applications. On Nova 4/5 the answer is not this package: use native URL::make('Website') for editable links, or Text::make('Website')->displayUsing(...)->asHtml() for detail-page derived links. Both eliminate the dependency entirely and deliver an identical user outcome.
Decision gate before build work. Spend two minutes reading three files — src/*.php (does it extend Text, Field, or a menu provider?), resources/js/field.js (does it emit <a> with target="_blank" and rel="noopener noreferrer"? what does it render for null?), and composer.json (Nova constraint + license). That reading determines whether the next step is install, vendor, or replace, and it is the cheapest kill signal available.
Migration path — three options, ranked:
| Option | When | Cost | Residual risk |
|---|---|---|---|
Native replacement (Nova 4+) — URL field or Text + asHtml |
Default for any new work | Hours | None; dependency removed |
Vendor/fork into nova-components/external-url/ |
Existing Nova 2 apps that want the behavior | ~1 afternoon | We own the code, but no Composer constraint to fight |
| Install as dependency, pinned to commit SHA | Only if vendoring is blocked and the version matches | Minutes now | Abandoned dependency carried through every Nova upgrade |
For a legacy Nova 2 app that already uses it: leave it alone, document the owner, and budget roughly half a day to replace it during the next Nova upgrade rather than pre-emptively touching working code.
Compatibility and asset handling. Published assets require a public/vendor symlink; a blank column after composer install on a fresh machine is almost always a missing symlink or stale config:cache, not a code defect. Run php artisan nova:publish and php artisan optimize:clear before anyone reads the Vue source. If vendoring, confirm the license first.
Sequencing:
->onlyOnDetail() by default, ->rules('nullable','url','max:2048') at every call site, explicit read-only vs. editable call, explicit empty-state behavior.rel="noopener noreferrer", confirm @click.stop on index, confirm no user-supplied text flows through asHtml().Maintenance. There is no upstream. Treat maintenance cost as ours and non-zero: patching the rel attribute, adding validation defaults, and — the real line item — rewriting the Vue component if the application moves to Nova 4/5. That rewrite is already on the roadmap, so this package's honest cost is "carry until upgrade, then delete." Prefer vendoring precisely because it converts an abandoned-dependency risk into a small piece of owned code.
Support. Anticipate one recurring ticket class: "the link column is blank." The resolution is a runbook entry, not an engineering investigation — nova:publish, re-symlink public/vendor, optimize:clear. Write it once and point the service desk at it. Second ticket class: "the link goes nowhere" — trace to a null or malformed stored URL, which the url validation rule prevents at the call site.
Scaling. Runtime cost is negligible: one anchor per row, no queries beyond what the resource already loads. The scaling risk is organizational, not technical — this package is a template for how the team treats low-adoption Composer packages generally. Whatever rule is applied here (pin the SHA, require a named owner, require a replacement plan at upgrade) generalizes to the next dozen lookalikes. Set the rule now; it is cheaper than adjudicating each case later.
Failure modes and responses:
| Failure | Symptom | Response |
|---|---|---|
| Nova version mismatch | Field does not register or render | Pre-empted by the day-0 constraint check |
| Missing asset symlink / stale cache | Blank column in one environment | Runbook: nova:publish + optimize:clear |
| Null URL | Dangling # link or literal null on screen |
Compute null via resolveUsing or hideFromDetail |
| Index row-click hijack | Clicking the link opens the record, not the site | Keep detail-only, or patch @click.stop |
Unsanitized asHtml |
XSS in the admin panel | Never route user-supplied text through HTML rendering |
| Nova upgrade | Component silently breaks | Pre-budgeted replacement; not a surprise |
Ramp-up. Low. The entire surface is one PHP class and one Vue component; a developer already fluent in Nova is productive in under an hour by reading the source. The documentation is thin and partly inaccurate, so plan for source-reading rather than onboarding docs — and have whoever does the day-0 verification write the two-paragraph internal note that becomes the team's actual documentation.
How can I help you explore Laravel packages today?