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

Fm Tinymce Bundle Laravel Package

helios-ag/fm-tinymce-bundle

Symfony bundle that integrates the TinyMCE WYSIWYG editor into forms and templates, with configurable toolbars, asset handling, and optional file-manager support. It simplifies adding rich-text editing to Symfony admin interfaces and content forms.

View on GitHub
Deep Wiki
Context7

Getting Started

This is still a Symfony bundle, not a Laravel package — 3.0 does not change that. composer require helios-ag/fm-tinymce-bundle will pull the code into vendor/, but there is no service provider, no auto-discovery, and nothing will boot. It registers FM\TinyMCEBundle\FMTinyMCEBundle for Symfony's kernel and ships Twig templates. Treat it as reference material and a design blueprint, not a dependency. What you actually port is two things:

  1. The config tree in DependencyInjection/Configuration.php (what options exist and their defaults).
  2. The init-script template under Resources/views/ (what the emitted tinymce.init({...}) call looks like).

Read those two files first — they are the entire contract. Everything else in the repo is Symfony wiring you will not reuse. Read them at the 3.0 tag specifically: one of the 3.0 changes is a bump of the bundled TinyMCE assets ("Tinymce up"), so the emitted init script and default option names may differ from the 2.0-era examples you'll find in blog posts.

A minimal Laravel translation, in the order you'd build it:

npm i tinymce
php artisan vendor:publish --tag=... # or just create config/tinymce.php by hand
// config/tinymce.php — mirror the bundle's option names so docs/README examples stay transferable
return [
    'base_url' => '/vendor/tinymce',      // self-hosted; copied out of node_modules at build time
    'suffix'   => '.min',
    'config'   => [
        'menubar'      => false,
        'plugins'      => 'advlist autolink link image lists charmap preview code',
        'toolbar'      => 'undo redo | bold italic | bullist numlist | link image | code',
        'height'       => 400,
        'valid_elements' => '*[*]',
    ],
];

First use case: one body textarea in an admin CRUD form. Render the textarea normally, then emit the init call with a selector scoped to a wrapper class — same shape as the bundle's tinymce_init() Twig function, just a Blade component:

{{-- resources/views/components/tinymce.blade.php --}}
@props(['name', 'value' => ''])
<div class="tinymce-wrap" data-tinymce='@json(config("tinymce.config"))'>
    <textarea name="{{ $name }}" id="{{ $name }}">{{ $value }}</textarea>
</div>
// resources/js/tinymce.js — the equivalent of the bundle's emitted inline script
import tinymce from 'tinymce/tinymce';
import 'tinymce/themes/silver';
import 'tinymce/icons/default';
import 'tinymce/models/dom';
import 'tinymce/plugins/advlist'; /* ...one import per plugin in your toolbar, or nothing loads */

const boot = () => document.querySelectorAll('.tinymce-wrap').forEach(el => {
    const ta = el.querySelector('textarea');
    if (tinymce.get(ta.id)) return;
    tinymce.init({ ...JSON.parse(el.dataset.tinymce), target: ta });
});

document.addEventListener('DOMContentLoaded', boot);

Implementation Patterns

Config → provider. Use mergeConfigFrom + publishes so per-environment overrides work the way Symfony's fm_tinymce: tree did:

$this->mergeConfigFrom(__DIR__.'/../config/tinymce.php', 'tinymce');
$this->publishes([__DIR__.'/../config/tinymce.php' => config_path('tinymce.php')], 'tinymce');

Per-form overrides. The bundle's value was letting a controller/form class override theme, toolbar, plugins per instance rather than globally. Reproduce that with a component prop that shallow-merges into the global config:

<x-tinymce name="summary" :options="['toolbar' => 'bold italic', 'height' => 200]" />

Merge server-side (array_replace_recursive(config('tinymce.config'), $options)) and @json the result into the data- attribute. Never build the options array in JS from string concatenation.

Asset delivery. Don't import the whole editor through Vite — skins, icons, and plugins are loaded lazily by URL and Vite won't know about them. Copy the runtime directory into public/vendor/tinymce at build time (vite-plugin-static-copy or a postinstall cp) and set base_url to that path. This is exactly the role of the bundle's base_url option, and it's the single most common source of 404-on-skin failures.

Multiple editors per page. Always init via selector/target on a scoped wrapper, never a global textarea selector — the bundle's mode: textareas convenience selector is the pattern that breaks first when a page grows a search box.

Livewire / Inertia / Turbo. This is where day-to-day friction lives. TinyMCE mutates the DOM outside your framework's knowledge.

  • Livewire: wrap the editor in wire:ignore so morphs don't destroy it, and sync content manually on change:
    tinymce.init({ target: ta, setup(ed) {
        ed.on('change', () => ed.save()); // writes back to the textarea
        ed.on('blur',   () => Livewire.find(ta.closest('[wire\\:id]').getAttribute('wire:id'))
                                .set(ta.getAttribute('wire:model'), ed.getContent()));
    }});
    
    Without wire:ignore you get "editor already exists" on every re-render.
  • Inertia/Vue/React: init in onMounted, tinymce.remove() in onUnmounted. Keep the options object in a plain JS module, not reactive state.
  • Livewire livewire:navigated / Turbo turbo:load: re-run the boot function; the old listener won't fire on soft navigation.

Image/file uploads. The bundle punts on this; you'll wire TinyMCE's images_upload_handler to a Laravel route. Return { location: '...' }, include X-CSRF-TOKEN from a meta tag, and validate MIME server-side — the handler trusts whatever the browser sends.

Persistence and rendering. Store the HTML in a TEXT/LONGTEXT column. Sanitize on save with mews/purifier or HTMLPurifier (TinyMCE is an editor, not a sanitizer), then render with {!! !!}. Validate against the stripped length, not the raw string — max:2000 on raw HTML will reject a 900-character post full of markup.

Gotchas and Tips

  • It still cannot be installed into Laravel. 3.0 is Symfony-only; there is no Laravel bridge, no facade, no Blade views, no config publish tag. If you want a package, composer require any of the TinyMCE-wrapper Laravel packages, or just use tinymce from npm — the bundle's only real value here is its option naming and its init template.
  • What 3.0 actually changed. Bundled TinyMCE assets bumped (PR #74), assorted fixes (#75), completed installation docs (#77), and dev-tooling bumps (matthiasnoback/symfony-*-test to ^5.1/^6.0 and Codacy badges). All of that is Symfony-side or CI-side. Nothing in the release notes adds runtime features you'd port, and nothing removes an option name — but the editor major under Resources/public/ may have moved, so check which TinyMCE version 3.0 ships before copying defaults from it.
  • Version drift in the docs. Older README examples point at //tinymce.cachefly.net/4.0/tinymce.min.js; that CDN and that major version are long gone. The config key also moved between releases (config: in 2.x/3.x-era tags vs. tinymce_config: later). Always diff Configuration.php against the tag you're reading — 3.0's doc rewrite is the most current, but the README has been wrong before.
  • Licensing changed. TinyMCE 7 is GPL-2.0-or-later (or commercial). Self-hosting the npm package is fine for GPL-compatible projects; the Cloud CDN wants an API key and an approved domain list. Pick one deliberately before you ship — switching later means touching every form.
  • Plugin imports are silent failures. If a plugin name appears in plugins but you didn't import its module, TinyMCE logs a console warning and simply drops the button. Symptom: toolbar shows a gap. Check the console, not the config.
  • base_url must end with / and must contain skins/ui/oxide/, icons/default/, themes/silver/, models/dom/, and plugins/<name>/. Any one of them missing produces an unstyled editor or a blank iframe with a 404 you'll only see in the Network tab.
  • CSP. The editor injects inline styles and creates a blob-URL iframe. You'll need style-src 'unsafe-inline' and, if using the Cloud CDN, script-src https://cdn.tiny.cloud. Add frame-src 'self' blob: too.
  • Content filtering surprises. TinyMCE strips tags not in valid_elements. Custom components, <iframe> embeds, or data-* attributes vanish on save. Extend via extended_valid_elements rather than loosening to *[*] blindly.
  • Double init. Guard with if (tinymce.get(id)) return; or tinymce.remove('#'+id) before re-init. The error is thrown, not logged, and kills the rest of your boot loop.
  • Testing. Dusk/Pest browser tests can't type into the iframe directly — $browser->script("tinymce.get('body').setContent('<p>hi</p>')") before submitting. Assert on the stored HTML, not on the editor.
  • Extension point. In Symfony you'd override @FMTinyMCE/... templates to change the emitted script. The Laravel equivalent is publishing the Blade view (php artisan vendor:publish --tag=views) — keep the init script in one overridable partial rather than inlining it in three admin forms.
  • Maintained, but not for you. 3.0 confirms the maintainer is active (fixes, docs, CI badges), so the code won't rot — but with a handful of stars and effectively no dependents, it's still a one-person Symfony bundle with zero Laravel surface. Pin nothing, copy the ~40 lines you need, and move on.
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.
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel
draftsmaninc/draftsman
square-bit/laravel-volition
himito/laravel-mailman
htinlynn/genius-helper
maco-studios/hephaestus
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