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 Link Field Laravel Package

carlson/nova-link-field

A Laravel Nova field that renders a clickable link directly in your resource index and detail views. Pair a display label with a target URL, optionally open in a new tab, and skip raw HTML or custom components for simple link display.

View on GitHub
Deep Wiki
Context7

Getting Started

Understand what you're installing before you install it. carlson/nova-link-field is a presentational Nova field: it takes a string column that already exists on your model and renders it as <a href="…"> in index and detail views instead of as dead text. No migration, no cast, no storage behaviour, no model trait. If you're expecting URL validation on save or normalisation at the database layer, that expectation is wrong and you should correct it in the first five minutes.

Step 1 — compatibility gate. Last release was 2020-02-26, which puts it in the Nova 2/3, Vue 2, Laravel Mix era. Before doing anything else:

composer why-not carlson/nova-link-field   # what in your lockfile blocks it

If you're on Nova 4 or 5, the frontend is Vue 3 and this compiled bundle will not register. You will not get a loud Composer failure — you'll get a clean install and a silently blank column. Stop here and read the source as a specification instead (see Fork-and-vendor below).

Step 2 — read three files, in this order. Under 30 minutes, and cheaper than any debugging session this field will cost you:

  1. src/Link.php — the entire API surface. A few dozen lines: component(), and whatever setters they chose (text(), target(), openInNewTab() — the exact spelling matters). Read this before trusting any snippet, including the ones in this document.
  2. resources/js/components/IndexField.vue and DetailField.vue — confirms the registered component name and shows you exactly how href is bound. This is where you'll spot escaping problems and missing rel attributes.
  3. dist/js/field.js — confirm the compiled asset is committed. If it isn't, you're rebuilding JS on a 2020 Node toolchain, which is its own afternoon.

Step 3 — register the asset if auto-discovery doesn't fire. If the package declares extra.laravel.providers, you're done. If not:

// app/Providers/NovaServiceProvider.php
public function boot()
{
    parent::boot();

    Nova::script(
        'nova-link-field',
        __DIR__.'/../../vendor/carlson/nova-link-field/dist/js/field.js'
    );
}

Step 4 — first use case: make a stored URL clickable. This is ~95% of what the field will ever do for you. The pattern worth internalising is the display/edit split — keep a normal Text field for the form, add the Link field for display:

use Carlson\NovaLinkField\Link;

public function fields(Request $request)
{
    return [
        ID::make()->sortable(),
        Text::make('Name'),

        Text::make('Hosted Asset URL')
            ->onlyOnForms()
            ->rules('nullable', 'url'),

        Link::make('Hosted Asset URL')
            ->text('Open asset')
            ->target('_blank')
            ->exceptOnForms(),
    ];
}

Ship it to one resource, on one internal admin screen, and click through index → detail → edit. If all three render, you're done. If the column is blank, the checklist in Gotchas has the answer in order of likelihood.

Implementation Patterns

Display-only field paired with an editable Text field. The most robust integration, and the one you should reach for by default. Because both fields reference the same attribute, Nova resolves the same value; the onlyOnForms() / exceptOnForms() pair prevents a duplicate column appearing twice in the form:

Text::make('Documentation URL')
    ->onlyOnForms()
    ->rules('nullable', 'url'),

Link::make('Documentation URL')
    ->text('View docs')
    ->exceptOnForms(),

Note where the ->rules('url') sits — on the Text field. The Link field almost certainly has no validation of its own, and even the validation it does have would only apply on submit of a form it isn't rendered in.

Computed URLs belong in a model accessor, not in the Vue component. The highest-value use case is a Nova resource mirroring an object whose canonical home lives elsewhere — a storefront page, a DAM asset, a ticketing system record. Build the URL in PHP where it's testable, then hand the field a plain column name:

// app/Models/Product.php
public function getStorefrontUrlAttribute(): string
{
    return "https://shop.example.com/p/{$this->slug}";
}
Link::make('Storefront URL')->target('_blank'),

This sidesteps the question of whether a 2020-era setter accepts closures, and it means URL construction gets covered by your normal PHPUnit suite rather than by clicking around in staging.

Put the "where does this live?" block at the top of the detail view. The whole business case is click-distance: how many clicks between "found the record" and "verified the external artifact." Cluster the links, put them first, and let everything else follow:

public function fields(Request $request)
{
    return [
        ID::make()->sortable(),

        Link::make('External Record')->target('_blank'),
        Link::make('Source PDF')->target('_blank'),

        // …everything else
    ];
}

Fork-and-vendor is the recommended integration. With 22 stars, zero dependents, and no release in five years, the honest adoption model is copy, not depend. Two routes, both under a day:

# route A: bring the source into your own namespace
git clone [email protected]:andrewicarlson/nova-link-field.git \
  app/Nova/Fields/Link

# route B: scaffold first-party and port the behaviour
php artisan nova:field acme/link

Either way, the field ends up under your CI, your code review, and your composer audit. While you're in there, harden the two things a field of this vintage routinely gets wrong — scheme enforcement on the way in, and rel on the way out:

// src/Link.php
public static function allowedSchemes(): array
{
    return ['http', 'https', 'mailto'];
}
<!-- DetailField.vue / IndexField.vue -->
<a
  :href="field.value"
  target="_blank"
  rel="noopener noreferrer"
>{{ field.text || field.value }}</a>

Roll out one resource at a time. The field is cheap enough to sprinkle everywhere once it works, but the failure mode when it breaks is silent — an empty cell with no console error you'd notice. Enable it on one resource, hit it in staging, then widen. Don't add it to fifteen resources in a single PR.

Gotchas and Tips

A blank column is almost always a component-name mismatch or a missing asset. The two look identical in the browser. Check, in order: (1) Link::component() returns a string matching the name registered in your Vue entrypoint; (2) dist/js/field.js is actually requested — open DevTools Network on a Nova index page and confirm; (3) the console for a Vue "unknown custom element" warning. If the JS 404s, your Nova::script() path is wrong — very common when the vendor directory sits somewhere unusual in a monorepo.

Nova 4/5 will not mount this. Nova 4 reworked the frontend to Vue 3; a Nova 2/3-era compiled bundle uses Vue 2 mixins and won't register. The failure is worse than a hard Composer error because it looks like success. If you're on Nova 4+, read src/Link.php and the two .vue files, then reimplement as a first-party field — roughly 60–120 lines. Same behaviour, no dead dependency, and Nova upgrades become a one-place fix rather than waiting on a maintainer who hasn't shipped since 2020.

Treat the href as hostile input. A link field is the textbook stored-XSS surface in an admin panel. If the value can ever be set by a non-superadmin, imported from an upstream system, or seeded from an external API, assume someone will eventually put javascript:alert(document.cookie) in that column. Three cheap mitigations:

  • Validate on save — ->rules('nullable', 'url') on the paired Text field, plus a custom rule enforcing a scheme allowlist (http, https, mailto).
  • Escape on render — confirm the Vue template uses :href attribute binding (auto-escaped) rather than v-html or raw string concatenation.
  • Add rel="noopener noreferrer" whenever target="_blank" is set. Tabnabbing is a real vector on every outbound click, and a 2020-era component frequently omits it.

Method names in the README are the README's, not the source's. text(), target(), openInNewTab(), asLink() — variants of these exist across similar fields and the exact spelling matters. Grep src/Link.php before you write twenty fields against a setter that doesn't exist. If it isn't there, fall back to the model-accessor pattern and keep the field to Link::make('Column').

Auto-discovery is not universal in this era. Packages from 2020 predate universal Nova asset auto-discovery. If the field renders on one environment but not another, that's almost always a difference in which service providers booted — check bootstrap/cache/packages.php, then run php artisan package:discover and php artisan nova:publish if you've been fiddling with config.

Don't let it into composer.json on a modern stack. This is the single most important instruction here. Zero dependents means nobody has validated this against a real Nova upgrade — you'd be the first. If it drifts into your manifest as a transitive or casual dependency, it surfaces on your next dependency audit as an unmaintained package with a five-year gap, and you'll have that conversation at the worst possible moment. Fork it, vendor it, or skip it deliberately. Both endings are fine; the ambiguous middle is not.

Debugging checklist:

Symptom First thing to check
Column blank on index Component name mismatch, or dist/js/field.js not loaded
Renders on detail, blank on index Only IndexField.vue is broken — check its href binding
Works locally, blank in staging Asset path, Nova::script(), package discovery cache
Link works but doesn't open in a new tab target setter name — grep src/Link.php
Long URLs truncated The Vue component's CSS/text handling, not the field class
Console error right after a Nova upgrade Expected — field bundles are the most common Nova upgrade breakage

Extension points, ranked by effort. (1) Custom anchor label via the field's text setter — zero effort. (2) Computed URL via a model accessor — ten minutes, fully testable. (3) Scheme allowlist and rel hardening — a small edit to the Vue component plus a validation rule. (4) Multi-link or link-with-metadata — do not attempt to extend this field. It's a single-value presentational field by design; bolting on arrays means rewriting the Vue component, at which point you're building a first-party field, which — given the maintenance reality — was probably the right call from the start.

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.
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource