| Install | |
|---|---|
composer require digitonic/filament-rich-editor-tools |
|
| Latest Version: | 2.0.0 |
| PHP: | ^8.2|^8.3|^8.4|^8.5 |
This package integrates deeply with Filament 4/5 Panels 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.
Install the package via Composer:
composer require digitonic/filament-rich-editor-tools
The service provider will be auto-discovered. No manual registration is needed.
This package extends Filament 4/5 Panels Rich Editor through a comprehensive integration that:
RichEditorUtil class that ensures your editor and renderer configurations stay synchronizedRichContentRenderer for advanced content processingThe integration is designed to be seamless - simply replace your existing RichEditor::make() calls with RichEditorUtil::make() to access all enhanced features.
This package enhances Filament's Rich Editor through automatic registration and macros:
TipTap PHP extensions automatically registered on every Filament Rich Editor renderer instance:
Macros on Filament's RichContentRenderer for 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 TipTap EditorFour production-ready custom blocks (detailed below):
Migration command for legacy content conversion:
php artisan filament-rich-editor-tools:migrate-blocks to convert old TipTap blocks to Rich Editor formattiptapBlock → customBlock node typestextStyle marks to textColor marks for Filament 4 compatibilityUnified 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.
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
Block Migration:
tiptapBlock node types to customBlockattrs.type to attrs.idattrs.data to attrs.configColor 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).
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.
In your Filament form or page, to get our rich editor do the following:
RichEditorUtil::make('raw_content'),
In your Filament form or page, to get our renderer do the following:
RichEditorUtil::render($content);
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);
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.
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).
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.
Use the included trait in your Laravel model factories to generate valid Rich Editor JSON structures for tests and seed data.
use Digitonic\FilamentRichEditorTools\Database\Factories\Concerns\BuildsRichEditorContent;
use Illuminate\Database\Eloquent\Factories\Factory;
class PostFactory extends Factory
{
use BuildsRichEditorContent;
public function definition(): array
{
return [
'content' => $this->richEditorParagraphs(3),
];
}
}
The trait provides three helpers:
richEditorParagraph(?string $text = null): arrayrichEditorDocument(array $nodes): arrayrichEditorParagraphs(int $count = 1): arrayExample output for richEditorParagraph('Hello world'):
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "Hello world"
}
]
}
Example output for richEditorDocument([$this->richEditorParagraph('Hello world')]):
{
"type": "doc",
"content": [
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "Hello world"
}
]
}
]
}
Example output shape for richEditorParagraphs(2):
{
"type": "doc",
"content": [
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "Generated paragraph text..."
}
]
},
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "Generated paragraph text..."
}
]
}
]
}
richEditorParagraphs() uses $this->faker, so call it from within a standard Laravel Factory context where Faker is available.
This package ships with four production-ready custom blocks that extend the Rich Editor's functionality:
Creates visually appealing side-by-side comparison lists with checkmark and X icons.
Features:
Editor Configuration:
Output:
Embeds YouTube and Vimeo videos with full responsive support.
Features:
Supported Platforms:
Editor Configuration:
Output:
Embeds Twitter/X posts using Twitter's native embedding system.
Features:
Editor Configuration:
Output:
Dynamically generates a navigable table of contents from the current document.
Features:
Editor Configuration:
Output:
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.
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 outputThe 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.
Publish the configuration file to customize the package behavior:
php artisan vendor:publish --tag=filament-rich-editor-tools-config
This package includes Blade views used by the built-in custom blocks (Pros & Cons, Video, Twitter/X, Table of Contents).
If you want to override them, publish the views to your application:
php artisan vendor:publish --tag=filament-rich-editor-tools-views
This will publish to:
resources/views/vendor/filament-rich-editor-toolsreturn [
// 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
],
];
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 EditorThe package automatically merges your custom blocks with the four built-in blocks (Pros & Cons, Video, Twitter Embed, Table of Contents).
We recommend writing feature tests around your Filament pages/components and asserting the TOC output when relevant.
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');
});
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');
});
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"
Contributions are welcome. Please open issues or PRs describing your use case and proposed changes.
The MIT License (MIT). See LICENSE.md for details.