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

carlos-andres/nova-html-field

View on GitHub
Deep Wiki
Context7

Getting Started

Confirm the platform matrix before installing: PHP 8.1+, Laravel 10+, Nova 4 or 5. The package ships pre-compiled assets, so there is no npm install / npm run dev step — a real win on teams that don't maintain a Node toolchain in CI.

composer require <vendor>/nova-html-field   # confirm exact name in the README

There is no mandatory publish step. Publish only if you want to override the field's view or ship a purifier config file.

Requirements to verify on the host: the dom/libxml extensions (HTMLPurifier dependencies) and a writable cache directory for purifier definitions — this is the single most common first-run failure (see Gotchas).

Where to look first, in order:

  1. src/Html.php — the field class. Read content(), html(), when()/unless() to learn exact signatures, whether html() receives the request/resource, and where sanitization is applied.
  2. The purifier service/configuration resolver — confirms whether config is an array, a HTMLPurifier_Config instance, or a config key.
  3. resources/js/components/Field.vue — check whether output is rendered with v-html (it will be) and whether the component also renders in the index row.

First use case — a static, read-only panel on a detail page:

use Vendor\NovaHtmlField\Html;

public function fields(Request $request): array
{
    return [
        Html::make('Sync Notes')
            ->content('<p class="text-gray-500">Synced from the CRM every 15 minutes.</p>')
            ->onlyOnDetail(),
    ];
}

Note the onlyOnDetail(): an HTML field has no meaningful value to display in an index column, and leaving it visible there is almost never what you want. Treat onlyOnDetail() as the default until proven otherwise.

Implementation Patterns

Static vs. dynamic content. Use content() for copy that lives in code (help text, disclaimers, links to internal docs). Use html() for anything from the database or a third-party feed:

Html::make('Description')
    ->html(fn () => $this->resource->rendered_description)
    ->onlyOnDetail(),

Because html() is a closure, it is not evaluated during Nova's field serialization for other screens unless the field is visible there — but do not rely on that as a performance guarantee (see Gotchas).

Presentation conditions vs. authorization. These are two different tools and mixing them up causes subtle bugs:

// Authorization: does this user get to see it at all?
Html::make('Internal Notes')
    ->html(fn () => $this->resource->internal_html)
    ->canSee(fn (Request $request) => $request->user()->isAdmin()),

// Presentation: content exists but isn't relevant right now
Html::make('Deprecation Notice')
    ->content('<p>This record type is retired.</p>')
    ->when(fn () => $this->resource->isDeprecated()),

Use Nova's native canSee() for policy-driven visibility and when()/unless() for content-state conditions. If a condition is security-relevant, put it in canSee().

Custom purifier configuration. Reach for this when legitimate content disappears (embedded videos, tables, inline styles). Prefer a published config file over inlining arrays so all fields share one policy:

Html::make('Body')
    ->html(fn () => $this->resource->body_html)
    ->config([
        'HTML.Allowed' => 'p,br,strong,em,ul,ol,li,a[href|title|target],h2,h3,table,tr,td,th',
        'HTML.SafeIframe' => true,
        'URI.SafeIframeRegexp' => '%^https://(www\.)?(youtube\.com/embed/|vimeo\.com/)%',
        'Cache.DefinitionImpl' => null, // or a writable path; see Gotchas
    ]),

Keep HTML.Trusted off and never add script to the allowlist — the whole value proposition of the field is that the default is safe.

Markdown → HTML pipelines. Convert first, purify second. Do the Markdown conversion in an accessor (rendered_description) and hand the field the finished HTML; that keeps the conversion logic testable outside Nova and lets you cache the result.

DRY helper for recurring panels. Help panels multiply fast across resources. Wrap the common shape once:

// app/Nova/Fields/HelpPanel.php
public static function make(string $html): Html
{
    return Html::make(' ')->content($html)->onlyOnDetail()->hideWhenUpdating();
}

Testing. Split the concern in two: (a) unit-test the purifier behavior through the package's service or a small sanitize() wrapper, asserting <script> and onerror= are stripped and allowed tags survive; (b) feature-test the Nova detail endpoint with Laravel's HTTP test helpers and assert the sanitized fragment appears in the payload. Config regressions are the most likely failure mode, and they only show up in tests that exercise a real payload.

Gotchas and Tips

  • The purifier cache directory is the #1 setup failure. HTMLPurifier needs a writable directory for its definition cache. On read-only container filesystems or ephemeral Fargate/Lambda-style deployments this throws at render time. Either set Cache.DefinitionImpl to null (slower per request) or point Cache.SerializerPath at a writable, writable-by-the-web-user path such as storage/app/htmlpurifier.
  • Silent stripping, not errors. Disallowed content vanishes without a warning. When "the HTML is blank," dump the raw value next to the rendered output before assuming the field is broken.
  • Inline styles are commonly stripped. Teams paste HTML from Word/Google Docs and lose all formatting. Decide explicitly whether CSS.AllowedProperties belongs in your config rather than relaxing HTML.Trusted.
  • Purification costs CPU. It is not a cheap string operation, and it runs per render. For high-traffic detail screens or HTML fields on wide index tables, cache purified output per record (Cache::remember keyed on model + updated_at) or store a pre-sanitized column at write time.
  • Closure evaluation vs. visibility. Verify in the field class the order in which html() is evaluated relative to when()/canSee(). If the closure touches relationships, eager-load them in the resource's fields()/query — otherwise a detail screen turns into an N+1.
  • Sanitization is display-only. The stored column remains raw. Your JSON API, transactional email, PDF export, and any {!! !!} Blade usage are still exposed. Sanitize at every output boundary, or sanitize on save and treat the column as trusted.
  • Index rows. HTML in a table cell breaks column alignment and can balloon row height. Default to onlyOnDetail(); if you genuinely need a snippet, render a trimmed plain-text summary instead.
  • No sort/filter/search. This is not a database-backed attribute. Searchables should be built from the underlying text column, and sorting on an HTML field will not behave as users expect.
  • Asset loading. Assets are pre-compiled — if the field renders unstyled or the component doesn't mount, check the browser console for 404s and confirm the service provider's Nova::script/Nova::style registration runs. Stale published assets left over from an earlier version are a frequent cause after upgrades; clear them and re-publish.
  • Don't fork the Vue component casually. Since you can't rebuild without re-establishing the package's front-end toolchain, prefer config and CSS overrides to editing resources/js files.
  • Config precedence is undocumented in the notes. Confirm whether a published config file, an inline config() array, or a passed HTMLPurifier_Config object wins, and whether inline values merge with or replace the file. This determines whether per-field overrides are safe.
  • Extension points to plan around: a shared base config in config/, a sanitize() helper for other output paths, and a cached accessor on the model. Those three cover nearly every day-to-day need without touching package internals.
  • Version pinning. At 1.0.0 there is no upgrade history to reason about; pin the minor version and watch for major HTMLPurifier bumps, which have historically changed allowed-tag behavior in ways that surface as content suddenly disappearing.
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.
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
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel
draftsmaninc/draftsman
square-bit/laravel-volition
himito/laravel-mailman
htinlynn/genius-helper
maco-studios/hephaestus
elmogy/larafast