digitonic / filament-rich-editor-tools
A package designed to improve the existing Filament Rich Editor
Installs: 2
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 0
Forks: 0
Open Issues: 0
pkg:composer/digitonic/filament-rich-editor-tools
Requires
- php: ^8.2|^8.3|^8.4
- filament/filament: ^4.0
- illuminate/contracts: ^11.0||^12.0
- spatie/laravel-package-tools: ^1.16
Requires (Dev)
- larastan/larastan: ^3.7
- laravel/framework: ^11.0|^12.0
- laravel/pint: ^1.21
- orchestra/testbench: ^9.0|^10.0
- pestphp/pest: ^4.0|^3.8
- pestphp/pest-plugin-arch: ^3.1
- pestphp/pest-plugin-laravel: ^3.0
- phpstan/extension-installer: ^1.4
README
This package integrates deeply with Filament 4's Rich Editor via macros and service provider hooks, effectively taking over and enhancing much of the core Rich Editor functionality to provide a more feature-rich experience.
Requirements
- PHP 8.4+
- Laravel 12
- Filament v4 Rich Editor
- Livewire v3
Installation
Install the package via Composer:
composer require digitonic/filament-rich-editor-tools
The service provider will be auto-discovered. No manual registration is needed.
Overview
This package extends Filament v4's Rich Editor through a comprehensive integration that:
- Automatically enhances all Rich Editor instances with additional functionality via Laravel's service container
- Provides a unified
RichEditorUtilclass that ensures your editor and renderer configurations stay synchronized - Registers custom blocks globally so they're available in any Rich Editor field
- Adds macro methods to
RichContentRendererfor advanced content processing - Handles heading ID assignment automatically for better SEO and navigation
The integration is designed to be seamless - simply replace your existing RichEditor::make() calls with RichEditorUtil::make() to access all enhanced features.
What it adds
This package enhances Filament's Rich Editor through automatic registration and macros:
-
TipTap PHP extensions automatically registered on every Filament Rich Editor renderer instance:
- Heading ID support for automatic anchor generation
- Enhanced table of contents generation capabilities
-
Macros on Filament's
RichContentRendererfor advanced content processing:toTableOfContents(int $maxDepth = 3): array— returns a nested TOC array from the current renderer contentprocessHeaderIds(Editor $editor, int $maxDepth = 3): void— assigns unique IDs to heading nodes inside a TipTapEditor
-
Four production-ready custom blocks (detailed below):
- Pros & Cons Block: Side-by-side comparison lists with icons
- Video Block: Embeddable YouTube and Vimeo videos with captions
- Twitter Embed Block: Native Twitter/X post embedding
- Table of Contents Block: Dynamic, navigable content outline
-
Migration command for legacy content conversion:
php artisan filament-rich-editor-tools:migrate-blocksto convert old TipTap blocks to Rich Editor format- Automatically migrates
tiptapBlock→customBlocknode types - Converts
textStylemarks totextColormarks for Filament 4 compatibility - Maps hex color values to Filament's named color system
-
Unified Editor/Renderer utilities that ensure consistency between editing and display modes
These features are registered globally and applied automatically to all Rich Editor instances in your application.
Usage
Convert your blocks
The migration command converts content from Filament 3's TiptapEditor format to Filament 4's RichEditor format:
php artisan filament-rich-editor-tools:migrate-blocks "App\Models\Page" blocks
Replace the model and field name as needed. This will overwrite existing DB records. So take a backup.
If you have a complex JSON structure with your rich editor located on something like meta.content you can use dot notation to specify the field.
php artisan filament-rich-editor-tools:migrate-blocks "App\Models\Page" meta.content
What the migration does
Block Migration:
- Converts
tiptapBlocknode types tocustomBlock - Renames
attrs.typetoattrs.id - Renames
attrs.datatoattrs.config - Normalizes block IDs (removes "Block" suffix, converts to lowercase)
Color Migration (textStyle → textColor):
When migrating from Filament 3, colored text stored with textStyle marks causes errors in Filament 4:
"There is no mark type textStyle in this schema"
The migration automatically converts:
// Before (Filament 3): { "type": "textStyle", "attrs": { "color": "#1E7F75" } } // After (Filament 4): { "type": "textColor", "attrs": { "data-color": "green" } }
The migration maps hex colors to Filament's named color system (red, orange, amber, yellow, lime, green, emerald, teal, cyan, sky, blue, indigo, violet, purple, fuchsia, pink, rose, gray).
Custom Color Mappings
If you have custom hex colors that need specific mappings, add them to your config:
// config/filament-rich-editor-tools.php return [ 'color_map' => [ '#1E7F75' => 'green', '#custom123' => 'brand', // Add more custom mappings as needed ], ];
For hex colors not in the map, the migration finds the closest matching color using RGB distance calculation.
Use our editor
In your Filament form or page, to get our rich editor do the following:
RichEditorUtil::make('raw_content'),
Use our Renderer
In your Filament form or page, to get our renderer do the following:
RichEditorUtil::render($content);
Render Types
The renderer supports multiple output formats via the RenderType enum:
use Digitonic\FilamentRichEditorTools\Enums\RenderType; use Digitonic\FilamentRichEditorTools\Filament\Utilities\RichEditorUtil; // Default HTML output (sanitized - safe for untrusted content) $html = RichEditorUtil::render($content, RenderType::HTML); // Unsafe HTML output (preserves iframes, scripts - use for trusted content only) $html = RichEditorUtil::render($content, RenderType::UNSAFE_HTML); // Plain text output $text = RichEditorUtil::render($content, RenderType::TEXT); // Array/JSON output $array = RichEditorUtil::render($content, RenderType::ARRAY); // Table of contents $toc = RichEditorUtil::render($content, RenderType::TOC); // Get the renderer instance for further customization $renderer = RichEditorUtil::render($content, RenderType::RENDERER);
When to use UNSAFE_HTML
By default, RenderType::HTML sanitizes the output and removes potentially dangerous elements like <iframe> tags. This is important for security when displaying user-generated content.
However, if you're using custom blocks that contain iframes (like the Video Block for YouTube/Vimeo embeds), you need to use RenderType::UNSAFE_HTML to preserve them:
// Video embeds will appear blank with sanitized HTML $html = RichEditorUtil::render($content, RenderType::HTML); // ❌ iframes stripped // Use UNSAFE_HTML to preserve video embeds $html = RichEditorUtil::render($content, RenderType::UNSAFE_HTML); // ✅ iframes preserved
⚠️ Security Warning: Only use UNSAFE_HTML for trusted content (e.g., admin-created content). Never use it for user-generated content where XSS attacks are a concern.
Generate a Table of Contents from content
Given a Filament RichEditor field or any HTML you pass to RichContentRenderer:
use Filament\Forms\Components\RichEditor\RichContentRenderer; $renderer = RichContentRenderer::make('<h1>Intro</h1><p>Text</p><h2>Details</h2>'); $toc = $renderer->toTableOfContents(maxDepth: 3); // Example structure: // [ // [ // 'id' => 'intro', // 'text' => 'Intro', // 'depth' => 1, // 'subs' => [ // ['id' => 'details', 'text' => 'Details', 'depth' => 2], // ], // ], // ]
The IDs are derived from the heading text and are deduplicated automatically (e.g., intro, intro-1, intro-2).
Assign stable IDs to headings in an Editor
If you’re working directly with a TipTap Editor, you can assign IDs to its headings:
use Filament\Forms\Components\RichEditor\RichContentRenderer; use Tiptap\Core\Editor; $editor = new Editor([ 'content' => '<h1>Intro</h1><h2>Details</h2>', ]); RichContentRenderer::make('')->processHeaderIds($editor, maxDepth: 3); // The editor now contains heading nodes with unique `id` attributes.
Custom Blocks
This package ships with four production-ready custom blocks that extend the Rich Editor's functionality:
1. Pros & Cons Block
Creates visually appealing side-by-side comparison lists with checkmark and X icons.
Features:
- Separate repeatable fields for pros and cons
- Green/red color coding with appropriate icons
- Responsive two-column layout
- Collapsible interface in the editor
Editor Configuration:
- Add unlimited pros and cons via repeater fields
- Each item has a simple text input
- Items are collapsible and cloneable for easy management
Output:
- Clean, professional layout with branded colors
- Checkmark icons (✓) for pros, X icons (✗) for cons
- Responsive design that stacks on mobile
2. Video Block
Embeds YouTube and Vimeo videos with full responsive support.
Features:
- Automatic detection of YouTube and Vimeo URLs
- Responsive iframe embedding
- Optional caption support
- Secure iframe attributes (no-referrer, allowfullscreen)
Supported Platforms:
- YouTube (youtube.com, youtu.be)
- Vimeo (vimeo.com)
- Requires YouTube Watch Links, not embed links.
Editor Configuration:
- URL field for the video link
- Optional caption field
Output:
- Full-width responsive video player
- 16:9 aspect ratio maintained
- Caption displayed below video if provided
3. Twitter Embed Block
Embeds Twitter/X posts using Twitter's native embedding system.
Features:
- Native Twitter widget integration
- Automatic URL conversion (x.com → twitter.com)
- Async script loading for performance
Editor Configuration:
- Single URL field for the Twitter/X post
Output:
- Native Twitter blockquote with full interactivity
- Preserves original post styling and functionality
- Responsive design
4. Table of Contents Block
Dynamically generates a navigable table of contents from the current document.
Features:
- Automatic generation from document headings
- Hierarchical structure with proper indentation
- Click-to-navigate functionality
- Customizable depth levels
Editor Configuration:
- Automatically captures the current model and field information
- No manual configuration required
Output:
- Nested list structure with proper hierarchy
- Hover effects and visual feedback
- Smooth scroll navigation to sections
- Responsive indentation based on heading depth
Using Custom Blocks
All custom blocks are automatically registered when you use RichEditorUtil::make():
use Digitonic\FilamentRichEditorTools\Filament\Utilities\RichEditorUtil; // In your Filament form RichEditorUtil::make('content') ->label('Page Content'),
Blocks appear in the "Custom Blocks" toolbar button and can be inserted anywhere in your content.
Adding Your Own Custom Blocks
You can extend the available blocks by adding them to the configuration:
// config/filament-rich-editor-tools.php return [ 'custom_blocks' => [ App\Filament\CustomBlocks\MyCustomBlock::class, App\Filament\CustomBlocks\AnotherBlock::class, ], ];
Each custom block should extend Filament\Forms\Components\RichEditor\RichContentCustomBlock and implement:
getId(): Unique identifier for the blockgetLabel(): Display name in the editorconfigureEditorAction(): Form fields for block configurationtoPreviewHtml(): HTML shown in the editortoHtml(): Final rendered HTML output
Automatic plugin registration
The package service provider hooks into Laravel’s container and adds the Heading ID TipTap extension to every RichContentRenderer resolved by the container. You don’t need to manually add the plugin in your forms or pages.
Configuration
Publish the configuration file to customize the package behavior:
php artisan vendor:publish --tag=filament-rich-editor-tools-config
Available Configuration Options
return [ // Table of Contents settings 'table_of_contents' => [ 'enabled' => true, 'prefix' => '', // Add a prefix to all heading IDs (e.g., 'section-') ], // Add your own custom blocks 'custom_blocks' => [ App\Filament\CustomBlocks\CalloutBlock::class, App\Filament\CustomBlocks\CodeSnippetBlock::class, // Add as many as needed ], ];
Configuration Details
table_of_contents.enabled: Controls whether TOC functionality is activetable_of_contents.prefix: Adds a prefix to all generated heading IDs for namespace separationcustom_blocks: Array of custom block classes that extend the available blocks in your Rich Editor
The package automatically merges your custom blocks with the four built-in blocks (Pros & Cons, Video, Twitter Embed, Table of Contents).
Testing
We recommend writing feature tests around your Filament pages/components and asserting the TOC output when relevant.
Testing Table of Contents Generation
Example using Pest:
use Filament\Forms\Components\RichEditor\RichContentRenderer; it('builds a nested table of contents', function () { $renderer = RichContentRenderer::make('<h1>Intro</h1><h2>Details</h2><h2>More</h2>'); $toc = $renderer->toTableOfContents(); expect($toc) ->toBeArray() ->and($toc[0]['text'] ?? null)->toBe('Intro') ->and($toc[0]['subs'][0]['text'] ?? null)->toBe('Details'); });
Testing Custom Blocks in Filament
use Digitonic\FilamentRichEditorTools\Filament\Utilities\RichEditorUtil; it('can render pros and cons block', function () { $content = [ 'type' => 'doc', 'content' => [ [ 'type' => 'prosAndConsBlock', 'attrs' => [ 'pros' => [['text' => 'Great performance']], 'cons' => [['text' => 'Expensive']] ] ] ] ]; $rendered = RichEditorUtil::render($content); expect($rendered) ->toContain('Great performance') ->toContain('Expensive'); });
Testing Video Embedding
use Digitonic\FilamentRichEditorTools\Support\EmbeddableVideo; it('can embed youtube videos', function () { $video = new EmbeddableVideo('https://www.youtube.com/watch?v=dQw4w9WgXcQ'); expect($video->isEmbeddable())->toBeTrue(); expect($video->getEmbedUrl())->toContain('youtube.com/embed/'); });
Run your tests:
php artisan test --filter="rich editor"
Contributing
Contributions are welcome. Please open issues or PRs describing your use case and proposed changes.
License
The MIT License (MIT). See LICENSE.md for details.