joshmoreno/nova-html-field
joshmoreno/nova-html-field is a Laravel Nova custom field for rendering HTML content directly in Nova forms and detail views. Display rich text, embedded markup, or custom HTML with simple configuration, making it easy to add flexible content blocks to admin resources.
Install with Composer; Nova package discovery handles the service provider and asset registration:
composer require joshmoreno/nova-html-field
Where to look first:
NovaHtmlField::make('Label')->html('<p>...</p>') — verify the setter name in src/).src/NovaHtmlField.php to confirm it extends Laravel\Nova\Fields\Field and what meta keys it sets.resources/js/components/ (or the compiled dist/js/field.js) to see how the index/detail/form variants are rendered.Add it to any resource's fields() method. The first useful case is a help/instructions block above a group of fields on the create/update form:
use Joshmoreno\NovaHtmlField\NovaHtmlField;
public function fields(NovaRequest $request): array
{
return [
NovaHtmlField::make('form_help')
->html('<p class="text-sm text-gray-500">Fields marked * are required. Changes are audited.</p>')
->onlyOnForms(),
Text::make('Name')->rules('required'),
// ...
];
}
Because it renders on all three page types, the same field also works on the index (as a table cell) and detail views without extra configuration.
Static help text on forms. Use it to break up long forms with section headers or inline guidance. Pair with Nova::panel() or ->onlyOnForms() so it doesn't clutter the detail page.
Detail-page callouts. Render links to related resources, documentation URLs, or support instructions. Use ->onlyOnDetail() and return markup built from route helpers:
NovaHtmlField::make('docs')
->html('<a class="link-default" href="'.route('nova.docs').'" target="_blank">Read the runbook</a>')
->onlyOnDetail(),
Conditional banners. Combine with ->canSee() to hide/show based on the current resource state:
NovaHtmlField::make('warning')
->html('<div class="bg-yellow-100 p-2 rounded">This record is archived.</div>')
->canSee(fn (NovaRequest $request) => $request->resource?->isArchived()),
Reusable factories. Define a small helper on a base resource (or a trait) so every resource gets the same banner markup:
protected function infoBanner(string $text): NovaHtmlField
{
return NovaHtmlField::make('info_'.Str::slug($text))
->html('<p class="text-sm">'.e($text).'</p>')
->onlyOnForms();
}
The unique make() name (derived from the text) prevents Nova from deduplicating fields with the same attribute name on one layout.
Index cells. For index use, keep markup compact (a badge or icon) because the index row height and column width are constrained. If the package doesn't ship a width option, wrap in a <span> with an inline style or Tailwind class.
Layout integration. Place it in a panel for consistent spacing:
public function fields(NovaRequest $request)
{
return [
Nova::panel(fn () => [
NovaHtmlField::make('intro')->html('<h3>Contact info</h3>')->onlyOnForms(),
Text::make('Email'),
Text::make('Phone'),
], 'Contact'),
];
}
$request->input() or model attributes straight into ->html() without e(); the field has no XSS guard because it's intended for trusted markup. This is the biggest risk in day-to-day use.$fillable, don't attach ->rules(), and don't rely on it being saved. Nova treats it as a display-only field, but a column name collision (e.g. ->make('name')) will shadow a real attribute in the detail view.->hideFromIndex() or keep the markup to a single inline element.->sortable(), ->filterable(), and search won't do anything because there's no underlying attribute. Don't advertise them in a resource's $search array.composer.json for Nova version constraints before upgrading Nova. If the Vue component breaks after a Nova upgrade, fork or vendor the field locally and adjust the component rather than waiting on a release.php artisan nova:publish or check the package's dist/js/ exists; if not, npm install && npm run prod in the package directory.meta in the Nova response (/nova-api/{resource}) to confirm the HTML payload and the component name. If the component name differs from what's in app.js/field.js, register it in Nova::script()/Nova::style() in NovaServiceProvider.AlertField extends NovaHtmlField), or override ->withMeta() to add your own props and a matching Vue component. This is also the escape hatch if you need a markdown renderer or sanitizer.$this->actingAs($admin)->get('/nova/resources/...')) and assertSee() against a distinctive substring of your HTML; asserting full markup is brittle because the component may wrap it.How can I help you explore Laravel packages today?