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

Filament Tiptap Editor Laravel Package

ksquaredcoding/filament-tiptap-editor

A Tiptap-powered rich text editor field for Filament forms, bringing a clean WYSIWYG experience to Laravel admin panels with configurable toolbar controls, rich content editing, and straightforward integration into Filament resources.

View on GitHub
Deep Wiki
Context7

Getting Started

Requirements: PHP 8.2+, filament/filament ^4.0 or ^5.0, and this package at ^4.1 if you're on Filament 5 (4.0.x will not resolve against it).

composer require ksquaredcoding/filament-tiptap-editor

# config + views/blocks
php artisan vendor:publish --tag="filament-tiptap-editor-config"
php artisan vendor:publish --tag="filament-tiptap-editor-views"

# frontend assets (do this every time the package updates)
php artisan filament:assets

The service provider is auto-discovered by Filament; there is no manual panel registration unless you want to override the editor views.

Where to look first:

  1. config/filament-tiptap-editor.php — this is the whole package. profiles defines the toolbar button sets (default, minimal, all, plus anything you add), and the media disk/directory keys live here.
  2. The TiptapEditor field class — it's a standard Filament Field, so ->required(), ->columnSpanFull(), ->helperText() and friends all work.
  3. The published Blade views, only if you need to restyle the editor chrome.

First use case — swapping out RichEditor on a Post resource with zero schema changes because both persist an HTML string:

use FilamentTiptapEditor\TiptapEditor;

public static function form(Schema $schema): Schema
{
    return $schema->components([
        TextInput::make('title')->required(),
        TiptapEditor::make('body')
            ->profile('default')
            ->columnSpanFull()
            ->required(),
    ]);
}

A text/longText column is enough. Nothing to cast, nothing to migrate.

Implementation Patterns

Define profiles once, reuse everywhere. Declare named toolbars in config rather than repeating ->tools([...]) per form:

// config/filament-tiptap-editor.php
'profiles' => [
    'default' => [
        'toolbarButtons' => ['h2', 'h3', 'bold', 'italic', 'link', 'bulletList', 'orderedList', 'blockquote', 'attachFiles'],
        'maxContentWidth' => '5xl',
    ],
    'marketing' => [
        'toolbarButtons' => ['h1', 'h2', 'bold', 'italic', 'alignLeft', 'alignCenter', 'link', 'media', 'oembed', 'horizontalRule'],
    ],
],

Then ->profile('marketing') in the form. This keeps content authors inside a controlled set of tags — which matters later at render time.

Shared form schema object. For a CMS with Post, Page, and LandingPage, extract the field into an invokable schema class (App\Filament\Schemas\ContentField) so a toolbar change is one edit, not five.

Media and attachments. Configure the disk once in config, and confirm the disk has a public URL and that php artisan storage:link has run. In production this is the single most common "images upload but render broken" cause.

TiptapEditor::make('body')
    ->fileAttachmentsDisk('s3-public')
    ->fileAttachmentsDirectory('editor/'.tenant()->id)
    ->fileAttachmentsVisibility('public');

Per-tenant directories are worth doing from day one — retrofitting them means rewriting stored URLs in old content.

Rendering side. Don't {!! $post->body !!} blindly. Either sanitize with mews/purifier (HTMLPurifier with a Tiptap-shaped allowlist) on output, or use the package's rich-content renderer, which is the better option if you use blocks, oEmbed, or mentions — those need the renderer to turn JSON node data into markup. Wrap that in a single <x-rich-content :content="$post->body" /> component so the escape hatch lives in one auditable place.

Blocks for structured content. Register block classes in config, add the blocks tool to the profile, and create the matching Blade view per block. Treat block output as a design-system contract: any block that emits raw user HTML needs its own sanitization.

Testing. These are normal Filament form fields, so Livewire tests are straightforward:

livewire(EditPost::class, ['record' => $post->id])
    ->fillForm(['body' => '<p>Hello</p>'])
    ->call('save')
    ->assertHasNoFormErrors();

$this->assertStringContainsString('<p>Hello</p>', $post->refresh()->body);

Assert on the DB string, not the rendered editor — Tiptap normalizes HTML in the browser, so client-side round-trips can reorder attributes.

Gotchas and Tips

  • Re-publish assets after every composer update. A blank editor with a working toolbar-less DOM is almost always stale JS from public/js/ksquaredcoding/filament-tiptap-editor/. php artisan filament:assets && php artisan view:clear fixes it; CI/deploy scripts must include it or your first post-deploy bug report is guaranteed.
  • A published config file freezes the defaults. New toolbar buttons or options introduced in a release won't appear until you diff your published config against the package's. Keep it under version control and re-check it on minor bumps like 4.0.2 → 4.1.0.
  • Filament 5 support arrived in 4.1.0. Pin ^4.1 explicitly in composer.json — ^4.0 may resolve to a 4.0.x that constrains Filament to 4.x and produce a confusing composer why-not conflict with the rest of your Filament 5 plugins. Conversely, if any other editor plugin is still 4.x-only, you'll need to wait or drop it; version conflicts here surface as an unresolvable dependency tree, not a runtime error.
  • Tool names must match registered extensions. A typo in toolbarButtons ('bulletlist') doesn't throw — the button simply doesn't render. When a tool "disappears", check the config string before the JS.
  • Output format is sticky. If you switch to JSON output, every read path (accessors, API resources, search, mail templates) breaks until you cast to array/AsArrayObject and add a renderer. Decide and commit early; HTML is the low-friction default.
  • Disk renames orphan historical content. Stored media URLs are relative to the configured disk. Moving public → S3 later means backfilling files and keeping the same disk name, or rewriting stored HTML.
  • Livewire payload bloat. Long documents (tables, pasted docs) inflate every Livewire round-trip on the form. For preview-only or read-only displays, use ->dehydrated(false) or a plain renderer instead of mounting a full editor.
  • CSP and nonce setups are the sneaky failure mode: the toolbar renders but typing is dead because the editor's inline init script was blocked. Check the browser console before debugging PHP.
  • Extension points, in order of preference: per-field ->tools() for one-off trims → named profiles in config for reuse → custom Tiptap extensions/buttons registered in config + a published view when you genuinely need new editor behavior. The last one couples you to the package's JS build, so budget for it.
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.
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin