富文本编辑器
简介
富文本编辑器可让你编辑和预览 HTML 内容,以及上传图片。它使用 TipTap 作为底层编辑器。
use Filament\Forms\Components\RichEditor;
RichEditor::make('content')

配置 Livewire 的最大嵌套深度
富文本编辑器以嵌套数据形式将其 TipTap 文档与 Livewire 同步。Livewire 默认将嵌套属性路径限制为 10 层,这对列表和表格等结构可能不够。若遇到 Livewire\Exceptions\MaxNestingDepthExceededException,且应用尚无 config/livewire.php 文件,请发布 Livewire 的配置文件:
php artisan livewire:publish --config该命令会覆盖已有的 config/livewire.php 文件,因此若已发布过配置请跳过。
然后,提高 config/livewire.php 中现有的 max_nesting_depth 设置。例如,深度 32 可为深度嵌套的富文本内容留出空间:
'payload' => [
// ...
'max_nesting_depth' => 32,
],请仅更改现有 payload 数组中的 max_nesting_depth 值,以保留 Livewire 其他与版本相关的 payload 设置。
以 JSON 存储内容
默认情况下,富文本编辑器以 HTML 存储内容。若希望改为以 JSON 存储,可以使用 json() 方法:
use Filament\Forms\Components\RichEditor;
RichEditor::make('content')
->json()该 JSON 采用 TipTap 格式,是内容的结构化表示。
若使用 Eloquent 保存 JSON 内容,请务必在模型属性上添加 array 转换(cast):
use Illuminate\Database\Eloquent\Model;
class Post extends Model
{
/**
* @return array<string, string>
*/
protected function casts(): array
{
return [
'content' => 'array',
];
}
// ...
}自定义工具栏按钮
你可以使用 toolbarButtons() 方法设置编辑器的工具栏按钮。此处显示的是默认选项:
use Filament\Forms\Components\RichEditor;
RichEditor::make('content')
->toolbarButtons([
['bold', 'italic', 'underline', 'strike', 'subscript', 'superscript', 'link'],
['h2', 'h3'],
['alignStart', 'alignCenter', 'alignEnd'],
['blockquote', 'codeBlock', 'bulletList', 'orderedList'],
['table', 'attachFiles'], // The `customBlocks` and `mergeTags` tools are also added here if those features are used.
['undo', 'redo'],
])主数组中的每个嵌套数组代表工具栏中的一组按钮。


工具栏中还可使用的其他工具包括:
h1- 对文本应用「h1」标签。h4- 对文本应用「h4」标签。h5- 对文本应用「h5」标签。h6- 对文本应用「h6」标签。alignJustify- 两端对齐文本。clearFormatting- 清除所选文本的所有格式。details- 插入<details>标签,允许用户在内容中创建可折叠区块。grid- 在编辑器中插入网格布局,允许用户创建响应式内容列。gridDelete- 删除当前网格布局。highlight- 用<mark>标签高亮所选文本。horizontalRule- 插入水平分隔线。lead- 在文本周围应用lead类,通常用于文章首段。paragraph- 将当前块设为段落,并移除任何标题格式。small- 对文本应用<small>标签,通常用于小号字体或免责声明。code- 将所选文本格式化为行内代码。textColor- 更改所选文本的文本颜色。table- 在编辑器中创建默认 3 列 2 行的表格,第一行配置为表头行。tableAddColumnBefore- 在当前列之前添加新列。tableAddColumnAfter- 在当前列之后添加新列。tableDeleteColumn- 删除当前列。tableAddRowBefore- 在当前行上方添加新行。tableAddRowAfter- 在当前行下方添加新行。tableDeleteRow- 删除当前行。tableMergeCells- 将所选单元格合并为一个。tableSplitCell- 将所选单元格拆分为多个单元格。tableToggleHeaderRow- 切换表格的表头行。tableToggleHeaderCell- 切换表格的表头单元格。tableDelete- 删除表格。
TIP
除了允许静态值外,toolbarButtons() 方法也接受函数以动态计算。你可以将各种工具作为参数注入到该函数中。
自定义浮动工具栏
若工具栏过于拥挤,可以使用浮动工具栏:仅当用户位于特定节点类型内时,在光标下方的工具栏中显示某些工具。这样可保持主工具栏简洁,同时在需要时仍能访问额外工具。
你可以使用 floatingToolbars() 方法自定义光标位于特定节点内时出现的浮动工具栏。
在下面的示例中,当光标位于段落节点内时会出现浮动工具栏,显示粗体、斜体等按钮。当光标在标题节点中时,显示与标题相关的按钮;在表格内时,显示表格专用控件。
use Filament\Forms\Components\RichEditor;
RichEditor::make('content')
->floatingToolbars([
'paragraph' => [
'bold', 'italic', 'underline', 'strike', 'subscript', 'superscript',
],
'heading' => [
'h1', 'h2', 'h3',
],
'table' => [
'tableAddColumnBefore', 'tableAddColumnAfter', 'tableDeleteColumn',
'tableAddRowBefore', 'tableAddRowAfter', 'tableDeleteRow',
'tableMergeCells', 'tableSplitCell',
'tableToggleHeaderRow', 'tableToggleHeaderCell',
'tableDelete',
],
])

将工具栏按钮分组到下拉菜单
你可以使用 ToolbarButtonGroup 将相关工具栏按钮分组到下拉菜单。第一个参数是用于下拉菜单提示与无障碍访问的标签,第二个参数是要包含在下拉菜单中的按钮名称数组:
use Filament\Forms\Components\RichEditor;
use Filament\Forms\Components\RichEditor\ToolbarButtonGroup;
RichEditor::make('content')
->toolbarButtons([
['bold', 'italic', 'underline', 'strike'],
[ToolbarButtonGroup::make('Paragraph', ['paragraph', 'h1', 'h2', 'h3'])],
[ToolbarButtonGroup::make('Alignment', ['alignStart', 'alignCenter', 'alignEnd', 'alignJustify'])],
['blockquote', 'codeBlock', 'bulletList', 'orderedList'],
['undo', 'redo'],
])默认情况下,第一个按钮的图标用作下拉触发器,并会响应式更新以反映当前激活的按钮。点击触发器会显示分组按钮。
你可以使用 icon() 方法为下拉触发器设置固定图标。设置自定义图标后,触发器图标保持静态,不会随激活按钮变化:
use Filament\Forms\Components\RichEditor;
use Filament\Forms\Components\RichEditor\ToolbarButtonGroup;
RichEditor::make('content')
->toolbarButtons([
['bold', 'italic', 'underline', 'strike'],
[ToolbarButtonGroup::make('Heading', ['h1', 'h2', 'h3'])->icon('fi-o-heading')],
[ToolbarButtonGroup::make('Alignment', ['alignStart', 'alignCenter', 'alignEnd', 'alignJustify'])],
['blockquote', 'codeBlock', 'bulletList', 'orderedList'],
['undo', 'redo'],
])

使用带文本的下拉工具栏按钮
默认情况下,下拉工具栏按钮仅显示图标。若希望在下拉项中同时显示图标与文本标签,可以在 ToolbarButtonGroup 上使用 textualButtons() 方法:
use Filament\Forms\Components\RichEditor;
use Filament\Forms\Components\RichEditor\ToolbarButtonGroup;
RichEditor::make('content')
->toolbarButtons([
['bold', 'italic', 'underline', 'strike', 'link'],
[ToolbarButtonGroup::make('Paragraph', ['paragraph', 'h1', 'h2', 'h3'])->textualButtons()],
[ToolbarButtonGroup::make('Alignment', ['alignStart', 'alignCenter', 'alignEnd', 'alignJustify'])],
['blockquote', 'codeBlock', 'bulletList', 'orderedList'],
['undo', 'redo'],
])

在此示例中,Paragraph 下拉项会同时显示图标与文本标签(例如「Paragraph」「Heading 1」)。Alignment 下拉菜单仍仅显示图标。
设置高度
你可以通过定义 minHeight() 和 maxHeight() 方法控制编辑器高度,它们接受任意 CSS 长度值:
use Filament\Forms\Components\RichEditor;
RichEditor::make('content')
->minHeight('12rem')
->maxHeight('24rem')编辑器默认最小高度为 10rem。内容超出 maxHeight() 后,编辑器停止增高并变为可滚动。每个方法可单独使用——minHeight() 设置起始高度同时仍允许增高,maxHeight() 限制最大高度。向 minHeight() 传入 null 可使用编辑器固有的 3rem 最小高度;向 maxHeight() 传入 null 可取消上限。这些约束在编辑器禁用时同样适用。
TIP
除了允许静态值外,minHeight() 和 maxHeight() 方法也接受函数以动态计算。你可以将各种工具作为参数注入到这些函数中。
自定义文本颜色
富文本编辑器包含用于设置行内文本样式的文本颜色工具。默认使用 Tailwind CSS 调色板。浅色模式下对文本应用 600 色阶,深色模式下使用 400 色阶。
你可以使用 textColors() 方法自定义选择器中可用的颜色:
use Filament\Forms\Components\RichEditor;
RichEditor::make('content')
->textColors([
'#ef4444' => 'Red',
'#10b981' => 'Green',
'#0ea5e9' => 'Sky',
])

若希望为浅色与深色模式定义不同颜色,可以使用 TextColor 对象定义颜色:
use Filament\Forms\Components\RichEditor;
use Filament\Forms\Components\RichEditor\TextColor;
RichEditor::make('content')
->textColors([
'brand' => TextColor::make('Brand', '#0ea5e9'),
'warning' => TextColor::make('Warning', '#f59e0b', darkColor: '#fbbf24'),
])若希望在现有 Tailwind 调色板上添加新颜色,可以将你的颜色合并到 TextColor::getDefaults() 数组中:
use Filament\Forms\Components\RichEditor;
use Filament\Forms\Components\RichEditor\TextColor;
RichEditor::make('content')
->textColors([
'brand' => TextColor::make('Brand', '#0ea5e9'),
'warning' => TextColor::make('Warning', '#f59e0b', darkColor: '#fbbf24'),
...TextColor::getDefaults(),
])使用 TextColor 对象时,数组的键会成为 <span> 标签上存储的 data-color 属性,便于在 CSS 中引用该颜色。当以颜色作为数组值时,实际颜色值(例如 HEX 字符串)会存储为 data-color 属性。
你还可以使用 customTextColors() 方法,允许用户选择预定义列表之外的自定义颜色:
use Filament\Forms\Components\RichEditor;
RichEditor::make('content')
->textColors([
// ...
])
->customTextColors()无需在内容渲染器上使用 customTextColors(),它会自动渲染内容中使用的任何自定义颜色。
渲染富文本内容
use Filament\Forms\Components\RichEditor\RichContentRenderer;
RichContentRenderer::make($record->content)->toHtml()toHtml() 方法返回字符串。若希望在 Blade 视图中输出 HTML 且不转义,可以不调用 toHtml() 而直接 echo RichContentRender 对象:
{{ \Filament\Forms\Components\RichEditor\RichContentRenderer::make($record->content) }}若已配置编辑器的文件附件行为以更改上传文件的磁盘或可见性,也必须将这些设置传给渲染器,以确保生成正确的 URL:
use Filament\Forms\Components\RichEditor\RichContentRenderer;
RichContentRenderer::make($record->content)
->fileAttachmentsDisk('s3')
->fileAttachmentsVisibility('private')
->toHtml()若在富文本编辑器中使用自定义块,可以向渲染器传入自定义块数组,以确保它们正确渲染:
use Filament\Forms\Components\RichEditor\RichContentRenderer;
RichContentRenderer::make($record->content)
->customBlocks([
HeroBlock::class => [
'categoryUrl' => $record->category->getUrl(),
],
CallToActionBlock::class,
])
->toHtml()若使用合并标签,可以传入用于替换合并标签的值数组:
use Filament\Forms\Components\RichEditor\RichContentRenderer;
RichContentRenderer::make($record->content)
->mergeTags([
'name' => $record->user->name,
'today' => now()->toFormattedDateString(),
])
->toHtml()若使用自定义文本颜色,可以向渲染器传入颜色数组,以确保颜色正确渲染:
use Filament\Forms\Components\RichEditor\RichContentRenderer;
use Filament\Forms\Components\RichEditor\TextColor;
RichContentRenderer::make($record->content)
->textColors([
'brand' => TextColor::make('Brand', '#0ea5e9', darkColor: '#38bdf8'),
])
->toHtml();为渲染内容设置样式
富文本编辑器的 HTML 根据所用功能,结合 HTML 元素、CSS 类与行内样式来呈现内容。若在 Filament 表格列或 infolist 条目中用 prose() 渲染内容,Filament 会自动应用所需样式。若在自己的 Blade 视图中输出内容,可能需要添加一些额外样式以确保正确显示。
为内容设置样式的一种方式是使用 Tailwind CSS Typography。该插件为标题、段落、列表、表格等常见 HTML 元素提供一组预定义样式。你可以使用 prose 类将这些样式应用到容器元素:
<div class="prose dark:prose-invert">
{!! \Filament\Forms\Components\RichEditor\RichContentRenderer::make($record->content) !!}
</div>不过,网格布局和文本颜色等功能需要 Tailwind CSS Typography 插件未包含的额外样式。Filament 还提供自己的 fi-prose CSS 类来补充这些样式。任何加载 Filament 的 vendor/filament/support/resources/css/index.css 的应用都可以使用该类。其样式与 prose 类不同,但更契合 Filament 的设计系统:
<div class="fi-prose">
{!! \Filament\Forms\Components\RichEditor\RichContentRenderer::make($record->content) !!}
</div>安全性
默认情况下,编辑器输出原始 HTML 并发送到后端。攻击者可能拦截组件的值并向后端发送不同的原始 HTML 字符串。因此,输出富文本编辑器的 HTML 时必须进行清理;否则站点可能面临跨站脚本(XSS)漏洞。
当 Filament 在 TextColumn 和 TextEntry 等组件中从数据库输出原始 HTML 时,会清理以移除危险的 JavaScript。但若在自己的 Blade 视图中输出富文本编辑器的 HTML,则由你负责。一种做法是使用 Filament 的 sanitizeHtml() 辅助方法,这与上述组件中用于清理 HTML 的工具相同:
{!! str($record->content)->sanitizeHtml() !!}DANGER
Filament 内置的 HTML 清理器允许行内 style 属性,以支持字体颜色、文本高亮、图片尺寸等富文本格式功能。这意味着 background: url(...) 或 position: fixed 等 CSS 属性不会从已清理的 HTML 中剥离。若内容来自不可信用户,应考虑限制默认配置。有关如何自定义清理器,请参阅安全文档。
向编辑器上传图片
默认情况下,上传的图片会公开存储在你的存储磁盘上,以便数据库中的富文本内容可在任意处轻松输出。你可以使用配置方法自定义图片上传方式:
use Filament\Forms\Components\RichEditor;
RichEditor::make('content')
->fileAttachmentsDisk('s3')
->fileAttachmentsDirectory('attachments')
->fileAttachmentsVisibility('private')TIP
除了允许静态值外,fileAttachmentsDisk()、fileAttachmentsDirectory() 和 fileAttachmentsVisibility() 方法也接受函数以动态计算。你可以将各种工具作为参数注入到该函数中。
TIP
Filament 还支持使用 spatie/laravel-medialibrary 存储富文本编辑器文件附件。更多信息请参阅我们的插件文档。
在编辑器中使用私有图片
在编辑器中使用私有图片会增加一层复杂性,因为私有图片无法通过永久 URL 直接访问。每次加载编辑器或渲染其内容时,都需要为每张图片生成临时 URL,且这些 URL 不会存入数据库。相反,Filament 会在图片标签上添加 data-id 属性,其中包含存储磁盘上该图片的标识符,以便按需生成临时 URL。
使用私有图片渲染内容时,请确保使用 Filament 中的 RichContentRenderer 工具 输出 HTML:
use Filament\Forms\Components\RichEditor\RichContentRenderer;
RichContentRenderer::make($record->content)
->fileAttachmentsDisk('s3')
->fileAttachmentsVisibility('private')
->toHtml()保护文件附件 ID
图片节点上的 data-id 属性是已配置磁盘上文件的标识符。渲染内容时,Filament 会为其生成 URL——若可见性为 private,则为签名临时 URL。与其他 Livewire 表单字段值一样,内容及其 data-id 属性由客户端控制:请求可被拦截,从而将 data-id 改为同一磁盘上的任意其他标识符。若该磁盘还存储属于其他用户或记录的文件,攻击者可能使渲染内容引用(并提供其签名 URL)他人的文件。
Filament 默认允许此行为,因为合法功能依赖它——例如从已有库插入图片的操作,或「从另一条记录复制」按钮。若你的编辑器都不依赖此类流程,可在字段上调用 preventFileAttachmentPathTampering() 以启用内置检查:
use Filament\Forms\Components\RichEditor;
RichEditor::make('content')
->preventFileAttachmentPathTampering()Filament 会解析记录的原始内容(通过 $record->getOriginal() 获取与字段名匹配的属性),并仅允许已有的 data-id 值。任何其他已有的 data-id 都会导致字段验证失败,因此记录绝不会以被篡改的值保存。新上传的图片始终可通过。
默认文件附件提供者不做按记录范围限定——除非启用 preventFileAttachmentPathTampering()(或在磁盘/目录级别隔离上传),否则任何解析到已配置磁盘上文件的 data-id 都会被接受。若改用 spatie/laravel-medialibrary 插件 作为文件附件提供者,此保护已隐式生效——它通过 $media->has($file) 对照记录自身的媒体集合查找每个 data-id,因此另一条记录媒体的 data-id 会自动被拒绝。
WARNING
preventFileAttachmentPathTampering() 需要表单上有记录。若没有(例如在创建页),除非 allowFilePathUsing 回调批准,否则每个已有的 data-id 都会验证失败。新上传不受影响。
若要在不逐个字段重复的情况下,对应用中每个 RichEditor 应用此检查,请在服务提供者的 boot() 方法中调用 configureUsing():
use Filament\Forms\Components\RichEditor;
RichEditor::configureUsing(function (RichEditor $component): void {
$component->preventFileAttachmentPathTampering();
});各个字段仍可通过调用 preventFileAttachmentPathTampering(false) 退出。
若应用合法地引用了记录上不存在的标识符——例如「从另一条记录复制」操作——请传入 allowFilePathUsing 参数以批准它。已批准的标识符会绕过验证错误:
use Filament\Forms\Components\RichEditor;
RichEditor::make('content')
->preventFileAttachmentPathTampering(
allowFilePathUsing: fn (string $file): bool => str_starts_with($file, 'templates/'),
)TIP
你可以将各种工具作为参数注入到传给 allowFilePathUsing 的函数中。
验证错误消息可通过 validationMessages() 使用 tampered 键自定义:
use Filament\Forms\Components\RichEditor;
RichEditor::make('content')
->preventFileAttachmentPathTampering()
->validationMessages([
'tampered' => 'The content references an image that is not permitted.',
])验证上传的图片
你可以使用 fileAttachmentsAcceptedFileTypes() 方法控制上传图片可接受的 MIME 类型列表。默认接受 image/png、image/jpeg、image/gif 和 image/webp:
use Filament\Forms\Components\RichEditor;
RichEditor::make('content')
->fileAttachmentsAcceptedFileTypes(['image/png', 'image/jpeg'])你可以使用 fileAttachmentsMaxSize() 方法控制上传图片的最大文件大小。大小以千字节指定。默认最大大小为 12288 KB(12 MB):
use Filament\Forms\Components\RichEditor;
RichEditor::make('content')
->fileAttachmentsMaxSize(5120) // 5 MB允许用户调整图片大小
默认情况下,用户无法调整编辑器中图片的大小。你可以使用 resizableImages() 方法启用图片调整大小:
use Filament\Forms\Components\RichEditor;
RichEditor::make('content')
->resizableImages()启用后,用户可通过点击图片并拖动调整手柄来调整大小。调整大小时始终保持宽高比。
TIP
除了允许静态值外,resizableImages() 方法也接受函数以动态计算。你可以将各种工具作为参数注入到该函数中。
使用自定义块
自定义块是用户可拖放到富文本编辑器中的元素。你可以使用 customBlocks() 方法定义用户可插入富文本编辑器的自定义块:
use Filament\Forms\Components\RichEditor;
RichEditor::make('content')
->customBlocks([
HeroBlock::class,
CallToActionBlock::class,
])

要创建自定义块,可以使用以下命令:
php artisan make:filament-rich-content-custom-block HeroBlock每个块需要一个继承 Filament\Forms\Components\RichEditor\RichContentCustomBlock 类的对应类。getId() 方法应返回块的唯一标识符,getLabel() 方法应返回将在编辑器侧栏中显示的标签:
use Filament\Forms\Components\RichEditor\RichContentCustomBlock;
class HeroBlock extends RichContentCustomBlock
{
public static function getId(): string
{
return 'hero';
}
public static function getLabel(): string
{
return 'Hero section';
}
}当用户将自定义块拖入编辑器时,你可以选择在插入块之前打开模态框以收集额外信息。为此,可以使用 configureEditorAction() 方法配置插入块时打开的模态框:
use Filament\Actions\Action;
use Filament\Forms\Components\RichEditor\RichContentCustomBlock;
class HeroBlock extends RichContentCustomBlock
{
// ...
public static function configureEditorAction(Action $action): Action
{
return $action
->modalDescription('Configure the hero section')
->schema([
TextInput::make('heading')
->required(),
TextInput::make('subheading'),
]);
}
}action 上的 schema() 方法可定义将在模态框中显示的表单字段。用户提交表单后,表单数据会保存为该块的「配置」。
为自定义块渲染预览
块插入编辑器后,你可以使用 toPreviewHtml() 方法为其定义「预览」。该方法应返回一段 HTML 字符串,在块插入时显示在编辑器中,让用户在保存前看到块的外观。你可以在此方法中访问块的 $config,其中包含插入块时在模态框中提交的数据:
use Filament\Forms\Components\RichEditor\RichContentCustomBlock;
class HeroBlock extends RichContentCustomBlock
{
// ...
/**
* @param array<string, mixed> $config
*/
public static function toPreviewHtml(array $config): string
{
return view('filament.forms.components.rich-editor.rich-content-custom-blocks.hero.preview', [
'heading' => $config['heading'],
'subheading' => $config['subheading'] ?? 'Default subheading',
])->render();
}
}若希望自定义编辑器中预览上方显示的标签,可以定义 getPreviewLabel()。默认使用 getLabel() 中定义的标签,但 getPreviewLabel() 能访问块的 $config,从而在标签中显示动态信息:
use Filament\Forms\Components\RichEditor\RichContentCustomBlock;
class HeroBlock extends RichContentCustomBlock
{
// ...
/**
* @param array<string, mixed> $config
*/
public static function getPreviewLabel(array $config): string
{
return "Hero section: {$config['heading']}";
}
}使用自定义块渲染内容
渲染富文本内容时,可以将自定义块数组传给 RichContentRenderer,以确保块正确渲染:
use Filament\Forms\Components\RichEditor\RichContentRenderer;
RichContentRenderer::make($record->content)
->customBlocks([
HeroBlock::class,
CallToActionBlock::class,
])
->toHtml()每个块类可以有一个 toHtml() 方法,返回应为该块渲染的 HTML:
use Filament\Forms\Components\RichEditor\RichContentCustomBlock;
class HeroBlock extends RichContentCustomBlock
{
// ...
/**
* @param array<string, mixed> $config
* @param array<string, mixed> $data
*/
public static function toHtml(array $config, array $data): string
{
return view('filament.forms.components.rich-editor.rich-content-custom-blocks.hero.index', [
'heading' => $config['heading'],
'subheading' => $config['subheading'],
'buttonLabel' => 'View category',
'buttonUrl' => $data['categoryUrl'],
])->render();
}
}如上所示,toHtml() 方法接收两个参数:$config(插入块时在模态框中提交的配置数据),以及 $data(渲染块可能需要的任何额外数据)。这让你能访问配置数据并相应渲染块。数据可在 customBlocks() 方法中传入:
use Filament\Forms\Components\RichEditor\RichContentRenderer;
RichContentRenderer::make($record->content)
->customBlocks([
HeroBlock::class => [
'categoryUrl' => $record->category->getUrl(),
],
CallToActionBlock::class,
])
->toHtml()分组自定义块
你可以使用 customBlocks() 数组中的字符串键将自定义块组织成分组。直接传入(无字符串键)的块不分组,并首先出现在面板中:
use Filament\Forms\Components\RichEditor;
RichEditor::make('content')
->customBlocks([
AlertBlock::class,
DividerBlock::class,
'Marketing' => [
HeroBlock::class,
CallToActionBlock::class,
BannerBlock::class,
],
'Media' => [
ImageGalleryBlock::class,
VideoEmbedBlock::class,
],
])

分组按数组中定义的顺序显示,侧栏中带粘性标题。
使用分组块渲染内容时,可以将相同的分组数组结构传给 RichContentRenderer。渲染时会忽略分组——仅使用块类:
use Filament\Forms\Components\RichEditor\RichContentRenderer;
RichContentRenderer::make($record->content)
->customBlocks([
'Marketing' => [
HeroBlock::class => [
'categoryUrl' => $record->category->getUrl(),
],
CallToActionBlock::class,
],
])
->toHtml()默认打开自定义块面板
若希望富文本编辑器加载时默认打开自定义块面板,可以使用 activePanel('customBlocks') 方法:
use Filament\Forms\Components\RichEditor;
RichEditor::make('content')
->customBlocks([
HeroBlock::class,
CallToActionBlock::class,
])
->activePanel('customBlocks')使用 prose 为自定义块预览设置样式
默认情况下,自定义块预览不应用 prose 样式,以便于自行设置样式。你可以使用 shouldApplyProseStylingToPreview() 方法为块预览启用 prose 样式。当你希望预览以标题、段落等排版样式显示时,这很有用:
use Filament\Forms\Components\RichEditor\RichContentCustomBlock;
class HeadingBlock extends RichContentCustomBlock
{
// ...
/**
* @param array<string, mixed> $config
*/
public static function shouldApplyProseStylingToPreview(array $config): bool
{
return true;
}
}当 shouldApplyProseStylingToPreview() 返回 true 时,块预览将应用富文本编辑器中定义的 prose 排版样式,包括适当的边距、字号及其他文本格式。该方法默认返回 false,因此预览以最少样式显示。
你可以根据块的配置做出此决定,让不同块具有不同的预览样式:
public static function shouldApplyProseStylingToPreview(array $config): bool
{
return ($config['useProseStyle'] ?? false) === true;
}使用合并标签
合并标签允许用户在富文本内容中插入「占位符」,渲染内容时可用动态值替换。这对插入当前用户姓名或当前日期等很有用。
要在编辑器上注册合并标签,请使用 mergeTags() 方法:
use Filament\Forms\Components\RichEditor;
RichEditor::make('content')
->mergeTags([
'name',
'today',
])

合并标签由双花括号包围,例如 {{ name }}。渲染内容时,这些标签会被替换为对应的值。
要将合并标签插入内容,用户可以开始输入 {{ 以搜索要插入的标签。或者,他们可以点击编辑器工具栏中的「merge tags」工具,打开包含所有合并标签的面板,然后从侧栏拖入内容或点击插入。
使用合并标签渲染内容
渲染富文本内容时,可以传入用于替换合并标签的值数组:
use Filament\Forms\Components\RichEditor\RichContentRenderer;
RichContentRenderer::make($record->content)
->mergeTags([
'name' => $record->user->name,
'today' => now()->toFormattedDateString(),
])
->toHtml()若有许多合并标签,或需要运行某些逻辑来确定值,可以将函数用作每个合并标签的值。该函数会在内容中首次遇到合并标签时调用,其结果会对同名后续标签缓存:
use Filament\Forms\Components\RichEditor\RichContentRenderer;
RichContentRenderer::make($record->content)
->mergeTags([
'name' => fn (): string => $record->user->name,
'today' => now()->toFormattedDateString(),
])
->toHtml()在合并标签中使用 HTML 内容
默认情况下,合并标签将其值渲染为纯文本。不过,你可以通过提供实现 Laravel Htmlable 接口的值,在合并标签中渲染 HTML 内容。这对插入格式化内容、链接或其他 HTML 元素很有用:
use Filament\Forms\Components\RichEditor\RichContentRenderer;
use Illuminate\Support\HtmlString;
RichContentRenderer::make($record->content)
->mergeTags([
'user_name' => $record->user->name, // Plain text
'user_profile_link' => new HtmlString('<a href="' . route('profile', $record->user) . '">View Profile</a>'),
])
->toHtml()当合并标签值实现 Htmlable 接口(例如 HtmlString)时,系统会自动检测并在不转义的情况下渲染 HTML 内容。非 Htmlable 值出于安全考虑仍渲染为纯文本。
使用自定义合并标签标签
你可以使用关联数组为合并标签提供自定义标签(显示在编辑器侧栏和内容预览中),其中键为合并标签名,值为标签文本:
use Filament\Forms\Components\RichEditor;
RichEditor::make('content')
->mergeTags([
'name' => 'Full name',
'today' => 'Today\'s date',
])这些标签不会保存在编辑器内容中,仅用于显示。
默认打开合并标签面板
若希望富文本编辑器加载时默认打开合并标签面板,可以使用 activePanel('mergeTags') 方法:
use Filament\Forms\Components\RichEditor;
RichEditor::make('content')
->mergeTags([
'name',
'today',
])
->activePanel('mergeTags')使用提及
提及允许用户通过输入触发字符插入对其他记录(如用户、议题或标签)的引用。当用户输入 @ 等触发字符时,会出现下拉菜单,供其搜索并从可用选项中选择。所选提及会作为不可编辑的行内 token 插入。
要在编辑器上注册提及,请使用 mentions() 方法并传入一个或多个 MentionProvider 实例:
use Filament\Forms\Components\RichEditor;
use Filament\Forms\Components\RichEditor\MentionProvider;
RichEditor::make('content')
->mentions([
MentionProvider::make('@')
->items([
1 => 'Jane Doe',
2 => 'John Smith',
]),
])

每个提供者配置有一个触发字符(传给 make()),用于激活提及搜索。你可以有多个带不同触发器的提供者:
use Filament\Forms\Components\RichEditor;
use Filament\Forms\Components\RichEditor\MentionProvider;
RichEditor::make('content')
->mentions([
MentionProvider::make('@')
->items([
1 => 'Jane Doe',
2 => 'John Smith',
]),
MentionProvider::make('#')
->items([
'bug' => 'Bug',
'feature' => 'Feature',
]),
])从数据库搜索提及
对于大型数据集,应使用 getSearchResultsUsing() 动态获取结果。回调接收搜索词,并应返回格式为 [id => label] 的选项数组。
使用动态搜索结果时,内容中仅存储提及的 id。要在加载内容时显示正确标签,还必须提供 getLabelsUsing()。该回调接收 ID 数组,并应返回格式为 [id => label] 的数组:
use Filament\Forms\Components\RichEditor;
use Filament\Forms\Components\RichEditor\MentionProvider;
RichEditor::make('content')
->mentions([
MentionProvider::make('@')
->getSearchResultsUsing(fn (string $search): array => User::query()
->where('name', 'like', "%{$search}%")
->orderBy('name')
->limit(10)
->pluck('name', 'id')
->all())
->getLabelsUsing(fn (array $ids): array => User::query()
->whereIn('id', $ids)
->pluck('name', 'id')
->all()),
])使用提及渲染内容
渲染富文本内容时,可以将提及提供者数组传给 RichContentRenderer,以确保提及正确渲染。
你可以使用 url() 方法使提及在渲染时链接到 URL。回调接收提及的 id 和 label,并应返回 URL 字符串:
use Filament\Forms\Components\RichEditor\MentionProvider;
use Filament\Forms\Components\RichEditor\RichContentRenderer;
RichContentRenderer::make($record->content)
->mentions([
MentionProvider::make('@')
->getLabelsUsing(fn (array $ids): array => User::query()
->whereIn('id', $ids)
->pluck('name', 'id')
->all())
->url(fn (string $id, string $label): string => route('users.show', $id)),
])
->toHtml()TIP
url() 闭包返回的字符串会直接渲染到 <a> 标签的 href 属性中,因此若 URL 的任何部分由用户输入构建,应确保它不能解析为浏览器会执行的 javascript: 或 data: 等方案。最简单的保证方式是用 Filament 的 Str::sanitizeUrl() 辅助方法包装返回值,它仅允许 http/https 和相对 URL:
use Illuminate\Support\Str;
->url(fn (string $id, string $label): ?string => Str::sanitizeUrl(
route('users.show', $id),
))若有意允许 javascript: URL(例如将提及连接到 Alpine.js 处理程序),请跳过该辅助方法并返回原始值——只需确保该 URL 的任何组成部分都不来自不可信用户输入。
注册富文本内容属性
富文本编辑器配置中有些部分同时适用于编辑器与渲染器。例如,若使用私有图片、自定义块、合并标签、提及或插件,需要确保两处使用相同配置。为此,Filament 提供了注册富文本内容属性的方式,可同时用于编辑器与渲染器。若插件实现了 HasFileAttachmentProvider,文件附件提供者会自动从插件解析,因此无需在属性或渲染器上调用 fileAttachmentProvider()。
要在 Eloquent 模型上注册富文本内容属性,应使用 InteractsWithRichContent trait 并实现 HasRichContent 接口。这允许你在 setUpRichContent() 方法中注册属性:
use Filament\Forms\Components\RichEditor\MentionProvider;
use Filament\Forms\Components\RichEditor\Models\Concerns\InteractsWithRichContent;
use Filament\Forms\Components\RichEditor\Models\Contracts\HasRichContent;
use Illuminate\Database\Eloquent\Model;
class Post extends Model implements HasRichContent
{
use InteractsWithRichContent;
public function setUpRichContent(): void
{
$this->registerRichContent('content')
->fileAttachmentsDisk('s3')
->fileAttachmentsVisibility('private')
->customBlocks([
HeroBlock::class => [
'categoryUrl' => fn (): string => $this->category->getUrl(),
],
CallToActionBlock::class,
])
->mergeTags([
'name' => fn (): string => $this->user->name,
'today' => now()->toFormattedDateString(),
])
->mergeTagLabels([
'name' => 'Full name',
'today' => 'Today\'s date',
])
->mentions([
MentionProvider::make('@')
->items([
1 => 'Jane Doe',
2 => 'John Smith',
]),
])
->textColors([
'brand' => TextColor::make('Brand', '#0ea5e9', darkColor: '#38bdf8'),
])
->customTextColors()
->plugins([
HighlightRichContentPlugin::make(),
]);
}
}每当使用 RichEditor 组件时,都会使用为对应属性注册的配置:
use Filament\Forms\Components\RichEditor;
RichEditor::make('content')要使用给定配置轻松从模型渲染富文本 HTML,可以在模型上调用 renderRichContent() 方法并传入属性名:
{!! $record->renderRichContent('content') !!}或者,你可以获取 Htmlable 对象以在不转义 HTML 的情况下渲染:
{{ $record->getRichContentAttribute('content') }}use Filament\Infolists\Components\TextEntry;
use Filament\Tables\Columns\TextColumn;
TextColumn::make('content')
TextEntry::make('content')扩展富文本编辑器
你可以为富文本编辑器创建插件,以便向编辑器与渲染器添加自定义 TipTap 扩展,以及自定义工具栏按钮。创建一个实现 RichContentPlugin 接口的新类:
use Filament\Actions\Action;
use Filament\Forms\Components\ColorPicker;
use Filament\Forms\Components\RichEditor;
use Filament\Forms\Components\RichEditor\EditorCommand;
use Filament\Forms\Components\RichEditor\Plugins\Contracts\RichContentPlugin;
use Filament\Forms\Components\RichEditor\RichEditorTool;
use Filament\Support\Enums\Width;
use Filament\Support\Facades\FilamentAsset;
use Filament\Support\Icons\Heroicon;
use Tiptap\Core\Extension;
use Tiptap\Marks\Highlight;
class HighlightRichContentPlugin implements RichContentPlugin
{
public static function make(): static
{
return app(static::class);
}
/**
* @return array<Extension>
*/
public function getTipTapPhpExtensions(): array
{
// This method should return an array of PHP TipTap extension objects.
// See: https://github.com/ueberdosis/tiptap-php
return [
app(Highlight::class, [
'options' => ['multicolor' => true],
]),
];
}
/**
* @return array<string>
*/
public function getTipTapJsExtensions(): array
{
// This method should return an array of URLs to JavaScript files containing
// TipTap extensions that should be asynchronously loaded into the editor
// when the plugin is used.
return [
FilamentAsset::getScriptSrc('rich-content-plugins/highlight'),
];
}
/**
* @return array<RichEditorTool>
*/
public function getEditorTools(): array
{
// This method should return an array of `RichEditorTool` objects, which can then be
// used in the `toolbarButtons()` of the editor.
// The `jsHandler()` method allows you to access the TipTap editor instance
// through `$getEditor()`, and `chain()` any TipTap commands to it.
// See: https://tiptap.dev/docs/editor/api/commands
// The `action()` method allows you to run an action (registered in the `getEditorActions()`
// method) when the toolbar button is clicked. This allows you to open a modal to
// collect additional information from the user before running a command.
return [
RichEditorTool::make('highlight')
->jsHandler('$getEditor()?.chain().focus().toggleHighlight().run()')
->icon(Heroicon::CursorArrowRays),
RichEditorTool::make('highlightWithCustomColor')
->action(arguments: '{ color: $getEditor().getAttributes(\'highlight\')?.[\'data-color\'] }')
->icon(Heroicon::CursorArrowRipple),
];
}
/**
* @return array<Action>
*/
public function getEditorActions(): array
{
// This method should return an array of `Action` objects, which can be used by the tools
// registered in the `getEditorTools()` method. The name of the action should match
// the name of the tool that uses it.
// The `runCommands()` method allows you to run TipTap commands on the editor instance.
// It accepts an array of `EditorCommand` objects that define the command to run,
// as well as any arguments to pass to the command. You should also pass in the
// `editorSelection` argument, which is the current selection in the editor
// to apply the commands to.
return [
Action::make('highlightWithCustomColor')
->modalWidth(Width::Large)
->fillForm(fn (array $arguments): array => [
'color' => $arguments['color'] ?? null,
])
->schema([
ColorPicker::make('color'),
])
->action(function (array $arguments, array $data, RichEditor $component): void {
$component->runCommands(
[
EditorCommand::make(
'toggleHighlight',
arguments: [[
'color' => $data['color'],
]],
),
],
editorSelection: $arguments['editorSelection'],
);
}),
];
}
}你可以使用 plugins() 方法将插件注册到富文本编辑器和富文本内容渲染器:
use Filament\Forms\Components\RichEditor;
use Filament\Forms\Components\RichEditor\RichContentRenderer;
RichEditor::make('content')
->toolbarButtons([
['bold', 'highlight', 'highlightWithCustomColor'],
['h2', 'h3'],
['bulletList', 'orderedList'],
])
->plugins([
HighlightRichContentPlugin::make(),
])
RichContentRenderer::make($record->content)
->plugins([
HighlightRichContentPlugin::make(),
])从插件启用或禁用工具栏按钮
默认情况下,当插件通过 getEditorTools() 提供工具时,这些工具会被注册但不会自动显示在工具栏中。用户需要使用 toolbarButtons() 或 enableToolbarButtons() 手动添加它们。
若希望插件自动启用或禁用工具栏按钮,可以在实现 RichContentPlugin 的同时实现 HasToolbarButtons 接口。这是一个可选的独立接口:
use Filament\Forms\Components\RichEditor\Plugins\Contracts\HasToolbarButtons;
use Filament\Forms\Components\RichEditor\Plugins\Contracts\RichContentPlugin;
class HighlightRichContentPlugin implements RichContentPlugin, HasToolbarButtons
{
// ... other methods ...
/**
* @return array<string | array<string | array<string>>>
*/
public function getEnabledToolbarButtons(): array
{
return ['highlight', 'highlightWithCustomColor'];
}
/**
* @return array<string>
*/
public function getDisabledToolbarButtons(): array
{
return [];
}
}getEnabledToolbarButtons() 方法返回要添加到工具栏的按钮名称。getDisabledToolbarButtons() 方法返回要从工具栏移除的按钮名称。
插件的工具栏修改会在用户级修改之前应用。这意味着用户始终可以使用 enableToolbarButtons() 或 disableToolbarButtons() 覆盖插件行为:
RichEditor::make('content')
->plugins([
HighlightRichContentPlugin::make(),
])
->disableToolbarButtons(['highlightWithCustomColor'])设置 TipTap JavaScript 扩展
Filament 能够异步加载 TipTap 的 JavaScript 扩展。为此,你需要创建一个包含该扩展的 JavaScript 文件,并在你的插件的 getTipTapJsExtensions() 方法中注册它。
例如,若要使用 TipTap highlight 扩展,请先确保已安装:
npm install @tiptap/extension-highlight --save-dev然后,创建一个导入该扩展的 JavaScript 文件。在此示例中,在 resources/js/filament/rich-content-plugins 目录下创建名为 highlight.js 的文件,并添加以下代码:
import Highlight from '@tiptap/extension-highlight'
export default Highlight.configure({
multicolor: true,
})编译此文件的一种方式是使用 esbuild。你可以使用 npm 安装它:
npm install esbuild --save-dev你必须创建一个 esbuild 脚本来编译该文件。可以放在任意位置,例如 bin/build.js:
import * as esbuild from 'esbuild'
async function compile(options) {
const context = await esbuild.context(options)
await context.rebuild()
await context.dispose()
}
compile({
define: {
'process.env.NODE_ENV': `'production'`,
},
bundle: true,
mainFields: ['module', 'main'],
platform: 'neutral',
sourcemap: false,
sourcesContent: false,
treeShaking: true,
target: ['es2020'],
minify: true,
entryPoints: ['./resources/js/filament/rich-content-plugins/highlight.js'],
outfile: './resources/js/dist/filament/rich-content-plugins/highlight.js',
})如脚本底部所示,我们将名为 resources/js/filament/rich-content-plugins/highlight.js 的文件编译为 resources/js/dist/filament/rich-content-plugins/highlight.js。你可以根据需要更改这些路径,也可以编译任意数量的文件。
要运行脚本并将此文件编译到 resources/js/dist/filament/rich-content-plugins/highlight.js,请运行以下命令:
node bin/build.js你应在服务提供者(如 AppServiceProvider)的 boot() 方法中注册它,并使用 loadedOnRequest(),以便在页面加载富文本编辑器之前不会下载该文件:
use Filament\Support\Assets\Js;
use Filament\Support\Facades\FilamentAsset;
FilamentAsset::register([
Js::make('rich-content-plugins/highlight', __DIR__ . '/../../resources/js/dist/filament/rich-content-plugins/highlight.js')->loadedOnRequest(),
]);要将此新 JavaScript 文件发布到应用的 /public 目录以便提供服务,可以使用 filament:assets 命令:
php artisan filament:assets在插件对象中,getTipTapJsExtensions() 方法应返回你刚创建的 JavaScript 文件路径。既然已用 FilamentAsset 注册,你可以使用 getScriptSrc() 方法获取该文件的 URL:
use Filament\Support\Facades\FilamentAsset;
/**
* @return array<string>
*/
public function getTipTapJsExtensions(): array
{
return [
FilamentAsset::getScriptSrc('rich-content-plugins/highlight'),
];
}共享捆绑的 TipTap/ProseMirror 实例
当自定义 JavaScript 扩展从 @tiptap/core 或 @tiptap/pm/* 导入时,每个编译后的扩展都会包含这些包的一份副本。这会使每个扩展浪费约 150–200 KB,更重要的是会在页面上创建多个 ProseMirror 实例。由于 ProseMirror 严重依赖 instanceof 检查(针对 Node、Mark、Plugin、DecorationSet 等),捆绑自身模块副本的扩展可能无法与编辑器核心互操作。
为避免此问题,Filament 在 window.FilamentRichEditor.tiptap 上公开捆绑的 TipTap 和 ProseMirror 模块:
window.FilamentRichEditor.tiptap = {
core, // @tiptap/core
pmState, // @tiptap/pm/state
pmView, // @tiptap/pm/view
pmModel, // @tiptap/pm/model
}你可以在扩展中直接引用这些模块:
const { Node, mergeAttributes } = window.FilamentRichEditor.tiptap.core
const { Plugin, PluginKey } = window.FilamentRichEditor.tiptap.pmState
export default Node.create({
name: 'myExtension',
// ...
})或者,你可以配置构建以拦截对 @tiptap/core 和 @tiptap/pm/{state,view,model} 的导入,并在运行时从全局解析它们。这样你仍可在扩展源码中编写普通的 import 语句——其他 @tiptap/* 包(如 @tiptap/extension-highlight)仍按常规捆绑。以下 esbuild 插件在构建时检查每个被拦截包的真实具名导出,并将导入重写为从 window.FilamentRichEditor.tiptap 读取:
npm install --save-dev @tiptap/core @tiptap/pm// bin/build.js
import * as esbuild from 'esbuild'
const tiptapSharedPlugin = {
name: 'tiptap-shared',
setup(build) {
const keys = {
'@tiptap/core': 'core',
'@tiptap/pm/state': 'pmState',
'@tiptap/pm/view': 'pmView',
'@tiptap/pm/model': 'pmModel',
}
build.onResolve({ filter: /^@tiptap\/(core|pm\/(state|view|model))$/ }, (args) => ({
path: args.path,
namespace: 'tiptap-shared',
}))
build.onLoad({ filter: /.*/, namespace: 'tiptap-shared' }, async (args) => {
const realModule = await import(args.path)
const namedExports = Object.keys(realModule).filter(
(key) => key !== '__esModule' && key !== 'default',
)
const key = keys[args.path]
let code = `const __module = window.FilamentRichEditor.tiptap.${key};\n`
if (namedExports.length) {
code += `export const { ${namedExports.join(', ')} } = __module;\n`
}
code += `export default __module?.default ?? __module;\n`
return { contents: code, loader: 'js' }
})
},
}
esbuild.build({
// ...
plugins: [tiptapSharedPlugin],
entryPoints: ['./resources/js/filament/rich-content-plugins/my-extension.js'],
outfile: './resources/js/dist/filament/rich-content-plugins/my-extension.js',
})INFO
window.FilamentRichEditor.tiptap 在富文本编辑器包加载时赋值,这发生在获取 getTipTapJsExtensions() URL 之前。若需要在富文本编辑器尚未加载的上下文中使用这些模块,请改为捆绑你自己的副本。
上述 esbuild 插件在构建时从本地安装的 @tiptap/core 和 @tiptap/pm 读取具名导出,因此请使这些版本与 Filament 捆绑的版本大致同步——否则扩展中引用的较新具名导出在运行时可能为 undefined。