interaction-design-foundation/nova-html-code-field
A Laravel Nova field for editing HTML and code directly in your resources. Provides a syntax-highlighted editor with a clean, developer-friendly interface for managing markup in Nova forms and detail views.
Install into a Nova 4/5 app:
composer require interaction-design-foundation/nova-html-code-field
There is no migration, service provider, or config file to publish — it is a field class only. If the editor renders blank on first load, run php artisan nova:publish and hard-refresh; asset registration is the usual first-day snag.
Register it in a resource like any other field:
use InteractionDesignFoundation\NovaHtmlCodeField\HtmlCodeField;
public function fields(NovaRequest $request): array
{
return [
ID::make()->sortable(),
Text::make('Title'),
HtmlCodeField::make('Body HTML', 'body_html')
->rules('required', 'string', 'max:65535'),
];
}
Where to look first: the package's src/ directory for the field class (confirm the exact namespace/class name and the config methods — the README is thin, and the source is ~1 file), then its composer.json to see the laravel/nova constraint. That constraint tells you whether you can adopt on Nova 4 or whether 2.2.x is a hard Nova 5-only bump.
First use case: a landing_pages or email_templates resource with an existing longText column (body_html). You are replacing a plain Textarea with a readable, line-numbered editor for markup that a technical operator maintains. Point it at the column, add a max: rule sized to the column type, and you are done. Do not ship it pointing at anything publicly rendered until you've added sanitization (see below).
The honest framing for day one: this package gives you editing UI only. Everything else — validation, sanitization, rendering, preview — stays in your application code.
Basic editor with sizing. The field supports configurable height and optional full-screen mode:
HtmlCodeField::make('Body HTML', 'body_html')
->height(500) // editor height in px — verify method name in src/
->fullScreen() // opt-in fullscreen toggle
->rules('required', 'string', 'max:65535')
->help('Raw HTML. Content is rendered as-is — no sanitization.');
Keep it onlyOnForms() on resources where the index/detail view should show something humane (a title, a rendered preview) rather than a wall of markup:
HtmlCodeField::make('Body HTML', 'body_html')
->onlyOnForms()
->rules('required', 'string', 'max:65535'),
If you do want it on detail, accept that it shows the stored markup as text — the field is an editor, not a renderer.
Sanitize on write, not on read. This is the single most important pattern. The field stores verbatim bytes, so put the policy in a model mutator or a saving observer so every write path is covered — Nova, tinker, seeders, imports:
protected function bodyHtml(): Attribute
{
return Attribute::make(
set: fn (?string $value) => $value === null
? null
: Mews\Purifier\Facades\Purifier::clean($value, 'admin_html'),
);
}
Configure the admin_html purifier profile narrowly (allow p, a[href], ul/ol/li, h2-h4, img[src|alt] — and allowlist URL schemes). If you cannot sanitize at write time, sanitize at render time with the same allowlist; do not do neither.
Preview as a separate surface. The package has no live preview, so build one you control: a read-only Nova action or a small authenticated route that renders the stored HTML inside a sandboxed iframe (sandbox="allow-same-origin" with no allow-scripts) so a stray <script> cannot execute against your admin session. This is also the cleanest place to apply your sanitizer output.
Multiple HTML slots on one resource. Nothing stops you having header_html and footer_html fields; just give each its own rules() and keep per-field max: values in sync with the actual column types.
JSON / structured blobs. If your HTML lives inside a JSON column, use ->resolveUsing() / ->fillUsing() to read and write the nested key, or extract a dedicated column. Don't fight the field with accessors that reshape on read but not on write — you'll get content drift.
Column and validation sizing. Pair max: with the column:
HtmlCodeField::make('Body HTML')->rules('string', 'max:16000000'), // LONGTEXT
TEXT caps at ~64 KB; if your authors paste in base64 image data or long email templates, you'll get silent truncation. Use LONGTEXT and set the rule to match.
Upgrade sequencing. Bump Nova first in staging, then this field. Regression-test the form view specifically — Nova 5's Vue 3 layer is where custom fields break — then detail and index. Keep a version pin in composer.json and a one-line rollback note in the PR.
tidy or a linter in a queued job).php artisan nova:publish, hard refresh, check the browser console for 404s on editor assets, then check CSP.script-src policy you may need a nonce/hash allowance or a per-path exception for Nova's asset route. Test in the target browser before rollout, not after.composer.json rather than assuming ^4|^5.height()/fullScreen() (or equivalents) in src/ before writing them into a dozen resources — a typo surfaces as a BadMethodCallException at form render, not at boot.resolveUsing for display formatting. The field expects raw HTML in and raw HTML out; wrapping it in strip_tags for the index view will make the form re-save stripped content if you're not careful about which view is which.max: rule plus a documented "no inlined base64 images" convention saves you a slow Nova form later.How can I help you explore Laravel packages today?