reedware/nova-html-field
Laravel Nova field that renders raw HTML directly within resource forms and detail screens. Embed custom markup, links, or inline content alongside standard Nova fields to build richer, more flexible admin panels without writing custom components. Simple to install and configure.
Install and register
composer require reedware/nova-html-field
The package is a Nova field, so there is no separate service provider to register manually (Laravel package auto-discovery) and typically no nova:publish step. Confirm the composer.json constraint matches your Nova major version — the last tag is from October 2020, which places it in the Nova 3.x era.
First place to look
README.md for the exact setter name used to pass HTML in (commonly ->html(...) or ->content(...) — verify against the source before relying on it).src/Html.php (or similarly named field class) to see the $component string and any fluent configuration methods.resources/js/components/Field.vue — check whether it renders with v-html (raw HTML output) or plain interpolation (escaped). This single detail determines whether the field does what you expect.First use case
Drop it into a resource's fields() method wherever you currently abuse a Text field or a Heading to show static copy:
use Reedware\NovaHtmlField\Html;
public function fields(Request $request)
{
return [
Html::make('Instructions')
->html('<p>Upload a CSV with columns: <code>name</code>, <code>email</code>.</p>')
->onlyOnForms(),
// ... real fields
];
}
The immediate wins are onboarding copy inside a form, legal/disclaimer blocks on detail screens, and computed summary panels that would otherwise need a custom Vue component.
Static help text per form section
Html::make('Billing Notes')
->html(view('nova.partials.billing-notes')->render())
->onlyOnForms()
->hideFromIndex(),
Rendering a Blade partial keeps the markup out of PHP and lets you localize it (__()), reuse CSS classes from your Nova theme, and version the copy like any other view.
Dynamic HTML from model state
Because the field is resolved against the underlying resource, you can compute the HTML per record rather than hardcoding it:
Html::make('Status Banner')
->html(fn () => $this->resource->isOverdue()
? '<div class="text-red-500 font-bold">Overdue by '.$this->resource->days_late.' days</div>'
: '<div class="text-green-600">On track</div>')
->onlyOnDetail(),
If the package does not accept a closure, fall back to ->resolveUsing(fn ($value) => ...) or compute in the resource's fields() method before returning the array — both patterns are common across Nova fields and behave identically here.
Section dividers and grouping
A make() with an empty or single-character HTML payload is a cheap way to break a 20-field resource into scannable blocks without writing a custom panel component:
Html::make('Shipping')->html('<hr class="my-4"><h3 class="text-lg font-bold">Shipping</h3>')->onlyOnForms(),
Conditional visibility
Use Nova's standard canSee / canSeeWhen to gate sensitive copy (internal notes, pricing disclaimers) so it never ships to unauthorized users — the HTML is rendered server-side into the payload, so canSee is a genuine security boundary, not just a UI hint.
Mix with onlyOn* scopes
The field is display-only, so be explicit about scope: ->onlyOnDetail() for long-form content, ->onlyOnForms() for instructions, ->hideFromIndex() almost always (index table cells are narrow and raw markup looks broken there).
Raw HTML means raw XSS risk. If any part of the string comes from user input, escape it yourself: e($model->user_note), not $model->user_note. Composing HTML with sprintf and unescaped values is the realistic way this field becomes a stored-XSS vector. Prefer Blade partials, which escape by default.
It is not a data field. Do not add ->rules('required'), ->sortable(), or ->searchable(). There is no attribute backing it, so validation and sorting will either error or silently no-op depending on Nova internals. Keep it out of any fillable reasoning.
Check the Vue component's rendering mode. If the component interpolates rather than using v-html, your tags will appear as literal text. If it uses v-html, Nova's content-security-policy configuration may strip inline <script> — which is usually what you want, but it will also surprise you if you were embedding a widget.
Nova 4/5 compatibility is unlikely. A 2020 release targets Nova 3.x's component registration and Field base class. If you are on Nova 4+, expect the field to render blank or throw on component resolution; test in a branch before adopting, and be prepared to fork and update the nova constraint plus the Vue component to Nova 4's laravel-nova import structure. With zero stars and zero dependents, there is no community pressure keeping it current — treat it as vendored code you may need to maintain.
Empty make() name can break layouts. Nova uses the name for labels and column headers; passing '' may render a stray header cell in detail view. Use a real name and hide the label CSS-side if needed.
Index/detail rendering differs. Even with hideFromIndex(), verify the detail page — some Nova fields apply different wrapper padding in detail vs. form contexts, so a <hr>-based divider may look correct in one and cramped in the other.
Clear view cache when iterating. If you render Blade partials into the field, a stale view:clear-worthy cache will make you think the field is broken. php artisan view:clear after editing partials.
Extension point. Subclassing the field class is the cleanest way to add your own sugar (->asPanel(), ->asWarning()) that wraps the HTML in your theme's classes — cheaper than a full custom Nova component and keeps the package's Vue side untouched.
How can I help you explore Laravel packages today?