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

outl1ne/nova-trumbowyg-field

An Outl1ne Laravel Nova field that brings the lightweight Trumbowyg WYSIWYG editor into your admin resources. Add a clean, customizable rich-text input for editing HTML content without the weight of larger editor packages. Simple to install and use in Nova forms.

View on GitHub
Deep Wiki
Context7

Getting Started

Install it with Composer. The service provider is auto-discovered and there's no publish step — assets come from the package's dist/ directory:

composer require outl1ne/nova-trumbowyg-field

Then point the field at an existing TEXT / LONGTEXT column on a Nova resource:

use Outl1ne\NovaTrumbowygField\NovaTrumbowygField;

public function fields(Request $request)
{
    return [
        NovaTrumbowygField::make('Content')
            ->rules('nullable', 'string'),
    ];
}

Where to look first

  1. src/ — the field class. This is essentially the entire package; it's small enough to read in one sitting before you write any code. Do this first, because the README and the package description already disagree with each other: the description advertises Trumbowyg::make(), while the shipped class is NovaTrumbowygField under the Outl1ne\NovaTrumbowygField namespace. Trust the source for the exact class name, namespace, and whether configuration is a generic ->options([...]) passthrough or has dedicated helpers (->height(), ->toolbar()). Five minutes here saves an hour of guessing.
  2. dist/ — the compiled Trumbowyg JS/CSS and the Vue field component. When the editor renders as a bare textarea or with no styling, the browser Network tab tells you immediately whether these loaded.
  3. Upstream Trumbowyg docs for the option keys you'll be passing through: btns, plugins, lang, semantic, resetCss, autogrow, svgPath. The package is a thin wrapper; the real configuration surface is Trumbowyg's.

First use case

Swap a plain Textarea for this field on your Post / Page / Article resource. Before touching plugins or uploads, validate the full round trip: create a record with bold text, a link, and a list; save; reload the edit form; confirm the same HTML repopulates the editor. Also check the detail and index views — confirm whether they render the HTML (via ->asHtml() or similar) or dump raw markup, and add ->displayUsing() if it looks wrong. This single smoke test catches the overwhelming majority of integration problems — missing assets, malformed options, silent value casting — before they compound.

Implementation Patterns

Centralize configuration in your own base field class

Nearly all behavior is driven by an options array serialized to the frontend per field. Capture your toolbar and defaults once so resources stay declarative:

namespace App\Nova\Fields;

use Outl1ne\NovaTrumbowygField\NovaTrumbowygField;

class RichText extends NovaTrumbowygField
{
    public function __construct($name, $attribute = null, $resolveCallback = null)
    {
        parent::__construct($name, $attribute ?? str_replace(' ', '_', strtolower($name)), $resolveCallback);

        $this->options([
            'btns' => [
                ['viewHTML'],
                ['undo', 'redo'],
                ['formatting'],
                ['strong', 'em', 'del'],
                ['link'],
                ['unorderedList', 'orderedList'],
                ['removeformat'],
                ['fullscreen'],
            ],
            'semantic' => false,
            'resetCss' => true,
        ])->rules('nullable', 'string', 'max:65535');
    }
}

Every resource then uses RichText::make('Body'), and a toolbar change is a one-file edit. Keep the options array JSON-safe — strings, numbers, arrays only. Closures, models, or Carbon instances will break serialization silently.

Sanitize on write, not only on render

The field stores exactly the HTML the editor emits. Any editor can be bypassed via devtools, a direct API call, or a paste from Word. Treat the column as untrusted at the boundary:

static::saving(function ($post) {
    $post->body = clean($post->body); // mews/purifier / HTMLPurifier
});

Pair it with render-time escaping or a second sanitize pass on output, and define an explicit allowed-tag policy in config. Keeping the content model as plain HTML is also the cheap migration path to TipTap/ProseMirror later if you outgrow this.

Per-locale editors for translatable columns

The field inherits standard Nova text resolution, so with spatie/laravel-translatable you render one editor per locale:

foreach (config('translatable.locales') as $locale) {
    $fields[] = RichText::make(__('Content')." ({$locale})", "content->{$locale}")
        ->rules('nullable', 'string');
}

Verify ->resolveUsing() / ->fillUsing() behavior against your Nova major before rolling this out broadly — dotted-attribute handling is where translatable integrations usually break.

Lean on validation and help text for guardrails

There's no built-in sanitization, so use rules for structure and length and communicate limits in the UI:

RichText::make('Body')
    ->rules('nullable', 'string', 'max:65535')
    ->help('Basic formatting only. Images and tables are disabled.');

Do XSS filtering in the sanitizer, never in the validation ruleset — validation is UX, sanitization is security.

Add plugins deliberately

Enable colors, table, upload, or paste only on the resources that need them. Each adds bundle weight, jQuery surface, and CSP risk. If you enable upload, you own storage, URL generation, and delete handling — Trumbowyg's upload is callback-based and does not know about Laravel's filesystem, so budget a small custom Nova route or an inline upload: { serverPath: ... } handler.

Derive a text column for search and previews

Rich HTML pollutes full-text search with tag syntax and truncates badly in listings. Maintain a body_text column populated in the same saving observer via strip_tags(), and search that instead:

$post->body_text = trim(preg_replace('/\s+/', ' ', strip_tags($post->body)));

Rollout sequence

  1. Spike on one low-traffic resource in a branch.
  2. Validate render → save → round trip, plus paste-from-Word and translatable columns.
  3. Stand up the sanitization pipeline and the derived text column.
  4. Backfill existing plain-text/markdown rows via a one-off job if you're migrating.
  5. Pilot with one internal content team, then roll out resource by resource with a documented one-line rollback to Textarea.

Gotchas and Tips

  • jQuery re-enters Nova. Trumbowyg is jQuery-era, and this field reintroduces a jQuery global into an otherwise jQuery-free Nova 4/5 (Vue 3) bundle. If another add-on also bundles jQuery, expect version collisions and a dead editor. Watch the console for $ / jQuery conflicts and pin or alias if needed.
  • XSS is entirely yours. Never render {!! $model->body !!} on a public page without a sanitizer, and audit every endpoint that can write to the same column — including APIs that bypass Nova entirely.
  • CSP and inline init. If the admin enforces a Content-Security-Policy, inline script init (and possibly 'unsafe-eval' from jQuery) may be blocked. Test behind your real CSP before assuming it works; the failure mode is a blank field with only a console violation.
  • Missing assets after deploy. There's no publish step, but the provider must be discovered. Editor-as-plain-textarea in production while fine locally almost always means the package wasn't discovered under --no-dev. Run php artisan package:discover and check the lockfile.
  • Silent mount failure after a Nova upgrade. The classic symptom is a blank field or an uninitialized textarea with no error at all. Add a post-deploy or CI smoke check (load one edit page, assert the editor container mounts) so a Nova bump doesn't quietly degrade authoring for weeks.
  • Options must be JSON-serializable. Closures, models, or dates in ->options([...]) break the component silently or throw. Stick to primitives and arrays, and if a toolbar button mysteriously disappears, dump the serialized field payload first.
  • Pin the version explicitly. With 6 stars and 0 dependents the bus factor is one. Verify tags and the Nova constraint in its composer.json against your installed major, then lock it:
    "outl1ne/nova-trumbowyg-field": "2.0.*"
    
    Assume you may need to fork into a private package; keeping a small Nova field aligned with the frontend API is a bounded task.
  • Extend via your own subclass, never vendor/. Options are a passthrough to Trumbowyg, so the clean extension is the base class above — plus, if truly needed, a custom Vue component registered from your own resources/js/components. Edits inside vendor/ vanish on composer update.
  • Dark mode and theming. Trumbowyg ships light-theme CSS and will look wrong under a dark Nova. Add a small override stylesheet to your Nova assets rather than patching package CSS, so the fix survives upgrades.
  • Storage and search impact. HTML inflates columns (size your LONGTEXT accordingly) and pollutes full-text indexes with tag syntax — see the derived text column pattern above.
  • Debug checklist when the editor doesn't appear: (1) Network — did Trumbowyg JS/CSS load from dist/? (2) Console — jQuery conflict or CSP violation? (3) DOM — is the textarea present but uninitialized (options serialization failure)? (4) php artisan package:discover — is the provider registered? (5) Lockfile — does the package's Nova constraint match your installed major?
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.
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle