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 Custom Tiptap Extensions Laravel Package

wotz/filament-custom-tiptap-extensions

Adds custom TipTap extensions to Filament rich text editors, letting you register extra editor features such as custom nodes, marks, and toolbar tools. Extends Filament's built-in TipTap integration with configurable, reusable extensions for richer content editing.

View on GitHub
Deep Wiki
Context7

Getting Started

Pull the package in and wire it into a single editor first, rather than trying to upgrade every rich-text field at once.

composer require wotz/filament-custom-tiptap-extensions

Most Tiptap extension packages don't need asset publishing because Filament Tiptap ships the JS bridge — check the README for a vendor:publish step anyway, since tool icons/labels sometimes live in a Blade view.

The entry point is the FilamentTiptapEditor field. Register the extension and its matching tool together:

use FilamentTiptapEditor\TiptapEditor;
use Wotz\FilamentCustomTiptapExtensions\Extensions\Underline;
use Wotz\FilamentCustomTiptapExtensions\Tools\UnderlineButton;

TiptapEditor::make('body')
    ->profile('default')
    ->extensions([
        Underline::make(),
    ])
    ->tools([
        UnderlineButton::class,
    ]);

The first real use case is almost always "the client wants one more inline format that core Tiptap doesn't expose." Start with whichever extension corresponds to the smallest missing affordance (underline, highlight, sub/superscript, etc.), confirm the button appears, type some content, save the record, and inspect the stored HTML. That single loop validates the whole install before you scale it out.

If the extension only exists client-side in your head, read the package source under src/Extensions and src/Tools first — the class names and make() signature are the contract, and they're short enough to skim in a minute.

Implementation Patterns

Register once, globally, via a shared form schema. Rather than repeating ->extensions([...])->tools([...]) on every resource, extract a helper:

// app/Filament/Support/RichText.php
final class RichText
{
    public static function make(string $name = 'body'): TiptapEditor
    {
        return TiptapEditor::make($name)
            ->profile('default')
            ->extensions([
                Underline::make(),
                Highlight::make(),
                // ...
            ])
            ->tools([
                UnderlineButton::class,
                HighlightButton::class,
            ]);
    }
}

Every resource then calls RichText::make() and inherits the same schema, so a later upgrade is one file, not thirty.

Pair extension + tool deliberately. The extension is what Tiptap knows about the node/mark; the tool is the toolbar button. Forgetting either side is the #1 reason "it installed but nothing shows up." Treat them as a tuple and keep them next to each other in your helper.

Register custom tools on top of a profile, not instead of one. Profiles (default, minimal, all) already configure a baseline of extensions. Add yours as an overlay so you don't accidentally strip core behaviour:

TiptapEditor::make('body')
    ->profile('minimal')      // baseline
    ->extensions([...])       // additive
    ->tools([...]);           // additive

Render output with RichContentRenderer. Whatever HTML the extra marks produce, render it through the same renderer so the JSON/HTML round-trip stays consistent and any block-level extensions (details, anchors, etc.) get the markup they expect:

{!! (new RichContentRenderer($post->body))->render() !!}

Conditional tools per role. If a content editor shouldn't get the fancy blocks, gate the tools array:

->tools(fn () => auth()->user()->isAdmin() ? $adminTools : $basicTools)

This is the cheapest way to keep non-technical editors away from extensions whose output your frontend CSS hasn't caught up with.

Test at the boundary you own. You're not testing Tiptap itself — you're testing that your form accepts the value and that your renderer emits it. A feature test that livewire()->fillForm([...])->call('save') and asserts on the persisted HTML plus one Blade render snapshot catches the realistic failure modes (tool not registered, sanitizer stripping the tag, renderer dropping an attribute).

Gotchas and Tips

  • Version pinning is the whole game. This package wraps Tiptap's JS schema; filament/tiptap-editor pins its own Tiptap major. A minor bump on either side can produce "extension registered but silently ignored" in the browser with no PHP error. After every composer update, open one editor and click every button on your custom tools.
  • Silent failures live in the browser console. Extension registration errors in Tiptap rarely throw server-side. If a button appears but does nothing, open DevTools — you'll usually see a schema conflict or an unregistered node name.
  • Name collisions with built-in marks. If filament/tiptap-editor already registers an extension with the same name (underline and highlight are the classic offenders), you'll end up with two marks fighting for the same tag. Prefer the package's extension only where core doesn't cover it, and don't stack both.
  • The tool needs an icon that actually exists. Filament tool icons resolve through the Blade icon set; a typo renders an empty button that still occupies toolbar space and looks like a CSS bug. Check the tool class for the default icon before assuming styling is broken.
  • Storage format matters more than you think. If your body column already contains HTML from a plain RichEditor, switching to TiptapEditor gives you JSON by default. Decide intentionally — ->output(Output::HTML) keeps the column readable and your frontend unchanged; JSON gives you richer structure but forces every consumer through the renderer.
  • Sanitizers will strip your new tags. If you run strip_tags or an HTML purifier anywhere in the pipeline, a new extension's output (details, marks with custom attributes) quietly disappears between save and publish. Extend the allowlist at the same time you add the extension.
  • Frontend CSS is your responsibility. These extensions add markup, not styling. Ship the corresponding selectors (.tiptap-highlight, details > summary, etc.) alongside the deploy, or editors will report the feature "doesn't work" when it's just unstyled.
  • Extension points for the impatient. If a button's behaviour is 90% right but the label or icon is wrong, subclass the tool class and override the presentation methods rather than forking the package — the tools are usually thin wrappers, and that keeps upgrades trivial.
  • Low bus factor, so vendor-lock lightly. With zero dependents and a tiny star count, assume maintenance is sporadic. Keep your registration helper thin (§Implementation Patterns) so replacing this package with hand-rolled extensions — they're a few dozen lines of Tiptap config each — is an afternoon, not a refactor.
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.
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk
martialbe/flysystem-oss
chondal/3312client
jeroen-g/flickr
opscale-co/nova-genbi
itsjustvita/laravel-retention
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator
equipc/laravel-email-database-log
adedsec/nova-translated-labels