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

froala/nova-froala-field

Froala Nova Field integrates the Froala WYSIWYG editor into Laravel Nova, giving your resources a powerful rich text field with image and file uploads, configurable toolbars, and custom editor options. Easily add, edit, and manage formatted content directly within your Nova admin.

View on GitHub
Deep Wiki
Context7

Getting Started

Check compatibility before anything else. This package is archived (last release January 2021) and targets the Nova 3.x / Laravel 7–8 / PHP 7.4–8.0 / Vue 2 window. Confirm what you're actually running:

composer show laravel/nova | grep versions
php artisan --version

If you're on Nova 4+, the bundled Vue 2 component will not mount. Stop there.

Install and locate the three things that matter.

composer require froala/nova-froala-field

There is no service provider, no publishable config, no migration. Everything lives in three places:

  1. vendor/froala/nova-froala-field/src/Froala.php — the field class. Read the options() method; it's the entire configuration surface.
  2. The compiled Vue component and bundled Froala JS in the package's dist/ (or resources/js/ + public/). This is where the editor version is pinned, and what you'd patch if you fork.
  3. The README options table — it maps 1:1 onto Froala's JS config object.

The mental model is one line: a Nova Field subclass that serializes a PHP array of Froala options into JSON and hands it to a compiled Vue component.

First use case: one resource, one HTML column.

// migration
$table->longText('body')->nullable();
// app/Nova/Post.php
use Froala\NovaFroalaField\Froala;

public function fields(Request $request)
{
    return [
        Froala::make('Body')
            ->options([
                'toolbarButtons' => [
                    'bold', 'italic', 'underline', '|',
                    'formatOL', 'formatUL', 'paragraphFormat',
                    'insertLink', 'insertImage', '|', 'html', 'fullscreen',
                ],
                'heightMin'       => 300,
                'language'        => 'en',
                'placeholderText' => 'Write the post…',
            ])
            ->rules(['nullable', 'string'])
            ->hideFromIndex(),
    ];
}

Verify the round trip in the first ten minutes. Create a post, save, reload the edit form, and inspect the raw column value in the database. You should see plain HTML — not JSON, not base64. That single property (plain HTML in a longText column) is what makes exiting this package cheap, and every downstream decision rests on it.

Then render it, sanitized, in Blade:

{!! $post->body !!}   {{-- only after purification, see Gotchas --}}

Implementation Patterns

Centralize the field, not the options. The biggest day-to-day cost of this package is that all configuration lives in ->options([...]) arrays repeated across resources. Subclass once:

// app/Nova/Fields/RichText.php
namespace App\Nova\Fields;

use Froala\NovaFroalaField\Froala;

class RichText extends Froala
{
    public function __construct($name, $attribute = null, $resolveCallback = null)
    {
        parent::__construct($name, $attribute, $resolveCallback);

        $this->options([
            'licenseKey'     => config('services.froala.key'),
            'toolbarButtons' => $this->toolbar(),
            'heightMin'      => 300,
            'imageUploadURL' => '/nova-api/uploads/images',
            'fileUploadURL'  => '/nova-api/uploads/files',
            'requestHeaders' => ['X-CSRF-TOKEN' => csrf_token()],
        ])->hideFromIndex();
    }

    protected function toolbar(): array { /* ... */ }
}

This is also your migration seam: when you swap to a maintained editor, you rewrite one class and every resource follows. Consider making the backend a config choice, with a plain Textarea fallback behind a feature flag.

Match toolbar presets to content type. A full toolbar on a 100-character meta description invites pasted Word documents with inline styles. Define two or three presets as subclass methods (->full(), ->minimal(), ->inline()) rather than hand-writing arrays per resource. Day-to-day, resources then read:

RichText::make('Body')->full(),
RichText::make('Excerpt')->minimal()->rules(['max:500']),

Froala's config keys are camelCase and exact. heightMin, toolbarButtons, imageUploadURL, paragraphFormat, placeholderText. They're passed through PHP as a plain associative array, serialized to JSON, and handed to the editor. Wrong casing or a typo doesn't throw — the option is silently ignored, which is how you lose 45 minutes staring at a blank toolbar.

Image uploads need your own endpoint. The field gives you imageUploadURL and nothing more. Froala's uploader POSTs multipart/form-data with a file key and expects exactly:

{ "link": "https://cdn.example.com/uploads/2024/06/photo.jpg" }

Any other shape — a wrapper envelope, a different key, a 200 with an error body — shows "Something went wrong" in the editor. Write the controller yourself:

// routes/nova-api.php, inside Nova's auth middleware
Route::post('/uploads/images', function (Request $request) {
    $request->validate([
        'file' => 'required|file|mimes:jpeg,png,webp,gif|max:5120',
    ]);

    $path = $request->file('file')->store('uploads/'.now()->format('Y/m'), 'public');

    return response()->json(['link' => Storage::disk('public')->url($path)]);
});

Two details that bite: it's a normal authenticated POST, so either send the CSRF token via Froala's requestHeaders (shown above) or exempt the route in VerifyCsrfToken. And validate MIME/size yourself — the editor will happily upload .svg with script inside. fileUploadURL follows the same { "link": ... } contract.

Sanitize at the write boundary, not just at render. Editor output is untrusted input. Purify in a mutator or observer so nothing — a seeder, a console command, a future API, an import script — can land unpurified HTML in that column:

// composer require mews/purifier
public function setBodyAttribute($value)
{
    $this->attributes['body'] = clean($value, 'froala');
}

With a froala profile in config/purifier.php allowlisting exactly the features you enabled: p br h1 h2 h3 h4 ul ol li a[href|title|rel] img[src|alt|width|height] strong em u blockquote table thead tbody tr td th pre code, style stripped or tightly allowlisted, and http/https only in URI.AllowedSchemes (blocks javascript: and data: links). Purifying on read as well is cheap defense-in-depth if other teams render the same column.

Prefer upload URLs over base64 paste. Froala will inline pasted images as base64 data URIs by default in some configurations. They bypass your upload endpoint, bloat the row, and can push past MySQL TEXT's 65 KB ceiling. Configure paste to route through the uploader, and use LONGTEXT if you can't prevent it.

Size-validate against the column. ->rules(['string', 'max:65535']) for TEXT, otherwise you get silent truncation on MySQL non-strict or a 500 on strict mode — days after launch, from one long article.

Keep a visible fallback. ->hideFromIndex() on every rich-text field, or Nova's index and detail views dump raw tags into table cells. Consider a read-only preview (->readonly() or a Text field showing Str::limit(strip_tags($value), 120)) so a broken editor never blocks the resource.

Testing is ordinary. The field is UI only; feature tests post HTML straight to your controller. Factories just need HTML strings. Reserve Dusk for one smoke test: "does the editor mount and save." That's a direct benefit of the plain-HTML storage decision.


Gotchas and Tips

The MIT license covers the wrapper, not the editor. Froala is commercial; production use requires a paid license keyed by domain. Without it, expect unlicensed/branding behaviour in production. Check whether the bundled build predates Froala 4 — the config key may be key (v3) or licenseKey (v4), and the wrong one fails silently. A paid Froala plan supports the editor, not this Nova integration; there is no support contract for the field itself.

Pin everything. laravel/nova, the Froala package, PHP and Laravel minors. There is no upstream compatibility work happening. Add the dependency to your register with a named owner and an explicit re-evaluation trigger (next Nova upgrade, or a Froala CVE) on the day you install it.

You cannot bump the bundled Froala JS independently. Editor assets ship inside the package at whatever shipped in 2021, and older Froala releases carry XSS/sanitization advisories. If your threat model doesn't tolerate that, vendor the package into your repo, replace the bundled Froala build, and rebuild the Vue component — otherwise accept the pinned version and lean hard on server-side purification.

Callbacks don't survive the wire. ->options([...]) becomes JSON, so Froala config values that are JS functions — events, custom imageUploadParams logic, fileUploadMethod handlers — can't be expressed. If you need custom editor behaviour, you're forking the Vue component. Budget that decision rather than discovering it mid-sprint.

Debugging a blank or missing field, in order:

  1. Browser console — a Vue mount error usually names the component and tells you immediately whether this is a Nova-version problem.
  2. Network tab — are the field's JS and Froala's assets loading at all? A 404 after php artisan nova:publish or a Nova minor bump is the classic failure mode.
  3. Hard refresh + php artisan view:clear; Nova caches its compiled asset manifest.
  4. Confirm the field is actually in fields() and not swallowed by an authorization callback.

Save failures with no visible error usually mean the HTML never reached the request — check that the column is nullable if empty values are allowed, and that no max rule is silently rejecting. Nova surfaces field errors on the form; a 500 means a malformed payload (often broken requestHeaders/CSRF config on upload).

height vs heightMin vs heightMax. Use heightMin so the editor grows with content. A fixed height produces a scrollbar inside a scrollbar on long articles — the #1 unsolicited complaint you'll get from editors.

Don't let it become load-bearing. Confine it to internal, low-exposure admin screens behind a feature flag, keep the native field as an immediate fallback, and keep content in plain HTML so switching editors is a UI change rather than a data migration. The moment content from this field flows into public-facing or compliance-sensitive pages, you've taken on a sanitization and CVE burden with no patch path — at that point the correct move is to replace the editor, not to harden the wrapper.

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.
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
xeviant/laravel-paystack
gorilladash/laravel-website-sdk