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 Tinyeditor Laravel Package

amidesfahani/filament-tinyeditor

A TinyMCE rich text editor integration for Filament Admin and Forms. Supports Filament 5.x, 4.x, and 3.x, with configurable profiles, toolbars, RTL direction, image and file attachments, custom external plugins, and resizable editing.

View on GitHub
Deep Wiki
Context7

Getting Started

1. Match the package major to your Filament major — do this before anything else.

Your stack Install
Filament 5 / Laravel 12 / PHP 8.2+ composer require amidesfahani/filament-tinyeditor:^5.0
Filament 4 / Laravel 11 / PHP 8.2+ composer require amidesfahani/filament-tinyeditor:^4.0
Filament 3 / Laravel 10+ / PHP 8.1+ composer require amidesfahani/filament-tinyeditor:^3.0

The package is a thin wrapper around a bundled TinyMCE build, so the version matrix is strict — there is no cross-major compatibility shim.

2. Publish assets (the step people skip and then debug for an hour).

php artisan vendor:publish --provider="AmidEsfahani\FilamentTinyEditor\TinyeditorServiceProvider"
php artisan vendor:publish --provider="AmidEsfahani\FilamentTinyEditor\TinyeditorServiceProvider" --tag="config"
php artisan vendor:publish --provider="AmidEsfahani\FilamentTinyEditor\TinyeditorServiceProvider" --tag="views"
php artisan vendor:publish --provider="AmidEsfahani\FilamentTinyEditor\TinyeditorServiceProvider" --tag="public"

If you skip the public tag you get a rendered textarea shell with no toolbar and tinymce is not defined in the console. The provider-level publish (no tag) covers everything, so in practice one command plus --tag="config" is enough.

3. First field — replace your RichEditor:

use AmidEsfahani\FilamentTinyEditor\TinyEditor;

TinyEditor::make('content')
    ->profile('simple')
    ->fileAttachmentsDisk('public')
    ->fileAttachmentsVisibility('public')
    ->fileAttachmentsDirectory('uploads/posts')
    ->columnSpanFull()
    ->required();

Run php artisan storage:link first, or inline images will 404.

Where to look first:

  • config/filament-tinyeditor.php — the profiles array is the real API surface. Everything else is a shortcut into it.
  • resources/views/tiny-editor.blade.php (published) — this is where the Alpine x-load / init directive lives; most integration bugs are visible here.
  • src/TinyEditor.php — since the README is terse, read the class for the full method list (setCustomConfigs, content_style, text patterns, dark mode, license key). It extends Filament's Field, so columnSpan, required, dehydrated, rules, helperText, disabled all behave normally.
  • Release notes for your major — the fix list (modal overlap, content loss on reorder, URL-encoded temp filenames, Alpine v2→v3 attribute rename) tells you which patch version you actually need.

Validate the install: open a form, confirm /js/tinymce/... (or the configured CDN) loads with 200s, type, add an image, save, and confirm the HTML round-trips into the DB.

Implementation Patterns

Profile-per-field instead of one global editor. Treat profiles as content contracts: minimal for short marketing blurbs, simple for comments/bio fields, default for article bodies, full for pages needing tables/media/anchors, none to drop the toolbar entirely. Pipe groups in the config let you visually separate tool clusters without writing custom JS:

'default' => [
    'plugins' => 'accordion autoresize directionality advlist link image lists code fullscreen media table emoticons',
    'toolbar' => 'undo redo removeformat | styles | bold italic | rtl ltr | alignjustify alignright aligncenter alignleft | numlist bullist outdent indent | blockquote table hr | image link media codesample emoticons | wordcount fullscreen',
    'upload_directory' => null,
    'custom_configs' => ['paste_webkit_styles' => 'color font-size'],
],

Use ->profile('custom') and add a custom key to profiles when you need one field that doesn't match any built-in.

Centralise field configuration. Because every resource would otherwise repeat disk/visibility/directory/profile, expose a static factory in a support class:

final class Editors
{
    public static function body(string $name = 'content'): TinyEditor
    {
        return TinyEditor::make($name)
            ->profile('default')
            ->fileAttachmentsDisk(config('filesystems.default'))
            ->fileAttachmentsVisibility('public')
            ->fileAttachmentsDirectory('cms/'.tenant('id').'/body')
            ->columnSpanFull();
    }
}

This is the single place you change when you migrate from local public to S3, or when you switch the disk to a private CDN-backed one.

Media handling. Three knobs, and they must agree: fileAttachmentsDisk() (Laravel disk name), fileAttachmentsVisibility() (must be public for inline <img> URLs to resolve), and fileAttachmentsDirectory() (per-profile upload_directory is the default when the field doesn't set one). For S3/R2, set visibility public and configure the disk's url so TinyMCE's image src points at the CDN, not s3.amazonaws.com.

RTL / multilingual. Two layers: the global 'direction' => 'rtl' config key for a whole Arabic/Hebrew admin panel, and ->rtl() / ->direction('auto|rtl|ltr') per field for mixed-language forms. Either way, directionality must be in the profile's plugins and rtl ltr in the toolbar, otherwise alignment buttons do nothing useful.

External plugins. Register third-party TinyMCE plugins by URL in the profile, then reference their buttons in toolbar:

'external_plugins' => [
    'paste_from_word' => 'https://unpkg.com/@pangaeatech/tinymce-paste-from-word-plugin@latest/index.js',
],

For anything intranet/offline, self-host the plugin file under public/ and point at a relative path — @latest from unpkg is a supply-chain and availability dependency.

Runtime custom configs. Beyond the config file, the field exposes a runtime custom-config setter (per the 2.0.9 "set custom config" change) plus content_style, which is the correct way to style the editor iframe body (fonts, line-height, code block background) so authored content matches front-end rendering. Confirm the exact method name against src/TinyEditor.php — the README doesn't document it.

Output side. The field stores raw HTML. In tables use TextColumn::make('content')->html()->limit(80) or strip tags; on the public site run the HTML through a sanitizer (HTMLPurifier / mews/purifier) and then render unescaped. Never {!! !!} untrusted editor output directly.

Testing. Livewire fillForm(['content' => '<p>hi</p>']) exercises the state contract fine, but it does not exercise TinyMCE. Anything involving toolbar behaviour, image upload or modal interaction needs Dusk/browser coverage; keep one smoke test that asserts the editor iframe exists.

Gotchas and Tips

  • Assets are the #1 failure mode. Missing --tag="public" publishes, a stale public/js/tinymce after a package upgrade, or a npm run build that wipes the vendor-published directory all produce a silent, toolbar-less editor. Re-publish and php artisan view:clear after every upgrade.
  • Version drift is silent. Installing ^4.0 on Filament 3 won't necessarily fail loudly at install time; it fails at render. Pin the minor ("^4.0.8") so you get the temp-upload filename fix and the modal-overlap fix rather than an early 4.0.x.
  • Private visibility breaks images. fileAttachmentsVisibility('private') with inline <img> tags gives you broken images or signed-URL churn. Public visibility, or a CDN disk with a stable url, is the only workable combination for a WYSIWYG.
  • Livewire re-render and content loss. The 2.0.8 fix addressed content loss on reorder, but repeaters, ->hidden() toggles, and tab switches still re-mount Alpine components. Keep the field dehydrated, give repeater rows stable keys, and test drag-reorder explicitly if you use it inside a Repeater.
  • Nested modals / z-index. TinyMCE's dialogs (link, image, table) float above the page; a Filament action modal opening the editor is the classic clash. Fixed in 2.0.5, but if you run a custom panel theme or additional modal packages, check the stacking context before assuming the package is at fault.
  • Paste is an injection vector. Word/Google Docs paste brings inline styles, mso- classes and sometimes base64 images. Set paste_webkit_styles, consider valid_elements/invalid_elements via custom_configs, and always sanitize server-side — ->rules(['max:65535']) won't stop <script>.
  • Global config vs per-field ->rtl(). The config key wins at boot for the default direction; per-field calls override the instance. If a field stubbornly ignores ->direction(), check whether directionality is actually loaded in that profile.
  • Dark mode needs iframe-level styling. The v1.0.3 dark_mode config (auto|media|class|force|false) toggles TinyMCE's own skin, but your app's CSS variables do not cross into the editor iframe. Use content_style with explicit colors if editor content must match a dark front-end.
  • CDN vs bundled vs cloud. The package supports both a bundled TinyMCE build and Tiny cloud, with a licence-key setting for the latter. If you're on cloud, the key must be present in config in every environment, including CI/staging, or the editor degrades to a read-only shell.
  • Config caching. Any change to config/filament-tinyeditor.php requires php artisan config:clear locally and a redeploy/config rebuild in production. Profile edits that "don't apply" are almost always this.
  • Upgrades are breaking at major boundaries. v4 moved to native Filament 4 components and Livewire 3 (PHP 8.2+); v5 targets Filament 5. Re-publish views on every major — stale published Blade referencing ax-load instead of x-load is a known historical breakage.
  • Low adoption means you own the edge cases. 109 stars and zero dependents means few people have hit your combination of Repeater + S3 + nested modal + RTL. Budget for reading the source, and pin the exact version in composer.json so a surprise minor release can't move under you. The published views and config are your supported extension points — prefer overriding them over patching the vendor tree.
  • Debugging checklist: window.tinymce in console → 404s on /js/tinymce/* → inspect the rendered x-load/x-load-src attributes → check whether the field is inside a hidden container at init time → re-publish views → clear caches. That order resolves the large majority of reports.
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.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource