Installation:
composer require eckinox/tinymce-bundle
Ensure config/packages/tinymce.yaml exists (auto-generated by the bundle).
First Use Case:
// src/Form/PostType.php
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder->add('content', TinymceType::class, [
'attr' => ['toolbar' => 'bold italic | bullist numlist']
]);
}
{{ form_start(postForm) }}
{{ form_row(postForm.content) }}
{{ form_end(postForm) }}
Verify:
config/packages/tinymce.yaml (default settings).src/Form/TinymceType.php (if extending).{{ tinymce() }} and {{ tinymce_scripts() }} in templates.docs/file-upload-example.md (reference implementation).$builder->add('description', TinymceType::class, [
'attr' => [
'plugins' => 'lists link image',
'toolbar' => 'undo redo | styleselect | bold italic | alignleft aligncenter alignright | bullist numlist outdent indent | link image',
'images_upload_handler' => 'custom_handler', // Route name
],
'mapped' => false, // For non-persisted fields (e.g., preview)
]);
data_class in the form builder to bind directly to Eloquent models:
$builder->setAttribute('data_class', Post::class);
Reusable Components:
{% macro tinymceEditor(fieldName, config = {}) %}
{{ tinymce(form[fieldName].value, {
name: fieldName,
skin: app.tinymce.skin,
...config
}) }}
{% endmacro %}
Usage:
{{ _self.tinymceEditor('bio', {'toolbar': 'bold italic'}) }}
Conditional Loading:
{% if app.user.isAdmin %}
{{ tinymce_scripts() }}
{% endif %}
document.addEventListener('DOMContentLoaded', () => {
const editor = document.querySelector('tinymce-editor');
if (editor) {
editor.setAttribute('init', JSON.stringify({
setup: (editorApi) => {
editorApi.ui.registry.addButton('customButton', {
text: 'Custom Action',
onAction: () => alert('Triggered!')
});
}
}));
}
});
# config/packages/tinymce.yaml
tinymce:
images_upload_route: 'app.tinymce_upload'
images_upload_route_params: { _locale: '%locale%' }
Controller:
#[Route('/upload', name: 'app.tinymce_upload', methods: ['POST'])]
public function upload(Request $request): JsonResponse
{
$file = $request->files->get('file');
$path = $this->uploadService->save($file);
return $this->json(['location' => $path]);
}
Blade Directives:
Add to app/Providers/AppServiceProvider.php:
Blade::directive('tinymce', function ($expression) {
return "<?php echo \Symfony\UX\TwigComponent\tinymce($expression); ?>";
});
Usage in Blade:
@tinymce($post->content)
Validation:
$builder->add('content', TinymceType::class, [
'constraints' => [
new Length(['min' => 100]),
new Assert\Regex('/<p[^>]*>.*<\/p>/', 'Content must include a paragraph.')
]
]);
Script Loading Conflicts:
tinymce_scripts() is called manually after a form renders.{% block tinymce_scripts %}{% endblock %} to centralize script inclusion.CORS Errors in Uploads:
Access-Control-Allow-Origin headers are missing.$response->headers->set('Access-Control-Allow-Origin', $request->headers->get('Origin'));
$response->headers->set('Access-Control-Allow-Credentials', 'true');
Form Data Binding:
name attributes are missing.name in attr:
'attr' => ['name' => 'post_content']
Skin/Theme Mismatches:
appstack) may not render correctly if CSS paths are wrong.content_css in tinymce.yaml matches the skin’s assets:
tinymce:
skin: appstack
content_css: "bundles/tinymce/css/appstack.css"
Plugin Dependencies:
image require TinyMCE’s images_upload_url to be set, even if unused.plugins config to reduce bundle size.console.error(tinymce.init.instance); // Debug editor instance
tinymce.min.js, appstack.css).{{ dump(form.vars) }} in Twig to inspect form field configurations.Custom Plugins:
init:
'attr' => [
'init' => json_encode([
'plugins' => 'customPlugin',
'customPlugin' => 'path/to/plugin.js'
])
]
Event Listeners:
document.querySelector('tinymce-editor').addEventListener('init', (e) => {
e.detail.editor.on('NodeChange', () => {
console.log('Content changed:', e.detail.editor.getContent());
});
});
Laravel Service Providers:
public function boot(): void
{
$this->app['tinymce.options']->set('toolbar', 'custom|toolbars');
}
Asset Management:
php artisan vendor:publish --tag=tinymce-assets
public/bundles/tinymce/.const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
tinymce.init({ selector: 'tinymce-editor' });
observer.unobserve(entry.target);
}
});
});
observer.observe(document.querySelector('tinymce-editor'));
config/packages/tinymce.yaml to reduce payload.php artisan route:clear
config/packages/tinymce.yaml:
assets_version: '{{ env("ASSET_VERSION") }}
How can I help you explore Laravel packages today?