acseo/pagebuilder-bundle
Symfony bundle that embeds a GrapesJS-based page builder. Provides Twig components to edit and render pages, plus a Page entity and controller to load/store HTML, CSS, and JSON. Configurable asset loading, plugins, and blocks.
Installation Run:
composer require acseo/pagebuilder-bundle
Enable the bundle in config/bundles.php:
ACSEO\PageBuilderBundle\PageBuilderBundle::class => ['all' => true],
Enable Routes
Add the bundle routes in config/routes/acseo_page_builder.yaml:
acseo_page_builder:
resource: '@PageBuilderBundle/src/Controller/'
type: attribute
Database Setup Run migrations:
php bin/console doctrine:migrations:diff
php bin/console doctrine:migrations:migrate
First Use Case Render a PageBuilder in a Twig template:
{{ component('PageBuilder', {'idField': 'page_content'}) }}
Render a saved page:
{{ component('PageRender', {'html': page.html}) }}
Frontend Integration
Use the PageBuilder Twig component in a Symfony form or standalone:
{{ component('PageBuilder', {
'idField': 'custom_field',
'config': {
'storageManager': false,
'assets': ['css': '/path/to/custom.css']
}
}) }}
Saving Pages
Leverage the PageController to store pages via HTTP requests:
// Example: POST /page/save
$page = new Page();
$page->setHtml($request->request->get('page_html'));
$page->setCss($request->request->get('page_css'));
$page->setConfig(json_encode($request->request->get('page_config')));
$em->persist($page);
$em->flush();
Rendering Dynamic Content
Use PageRender to dynamically render saved pages:
{% for page in pages %}
{{ component('PageRender', {
'html': page.html,
'css': page.css,
'config': page.config|json_decode
}) }}
{% endfor %}
config option in the Twig component.config parameter:
{{ component('PageBuilder', {
'config': {
'assets': [
'css': ['/vendor/grapesjs/dist/css/grapes.min.css'],
'js': ['/vendor/grapesjs/dist/js/grapes.min.js']
]
}
}) }}
FormTheme:
{% form_theme form _self %}
{% block form_row %}
{{ form_widget(form) }}
{% if form.vars.idField is defined %}
{{ component('PageBuilder', {'idField': form.vars.idField}) }}
{% endif %}
{% endblock %}
StorageManager Conflicts
If storageManager: true is set in the config, ensure your backend can handle GrapesJS’s storage API. Disable it for custom storage:
{{ component('PageBuilder', {'config': {'storageManager': false}}) }}
CSRF Issues
The PageController expects CSRF protection. Ensure your routes include _token fields:
{{ form_start(form, {'attr': {'novalidate': 'novalidate'}}) }}
{{ form_widget(form._token) }}
Asset Loading Order
GrapesJS requires CSS before JS. Use the assets config to enforce order:
{{ component('PageBuilder', {
'config': {
'assets': {
'css': ['/path/to/grapes.css'],
'js': ['/path/to/grapes.js']
}
}
}) }}
Page entity schema matches your migrations.idField is unique and matches your form/field names.Custom Blocks
Override GrapesJS blocks by extending the bundle’s assets or using the initBlock config:
{{ component('PageBuilder', {
'config': {
'initBlock': 'custom-block',
'blocks': [
{
'id': 'custom-block',
'label': 'Custom Block',
'content': '<div>Hello!</div>'
}
]
}
}) }}
Event Listeners
Hook into GrapesJS events via JavaScript in the config:
{{ component('PageBuilder', {
'config': {
'init': function(e) {
e.detail.editor.on('block:add', function(e) { console.log('Block added!'); });
}
}
}) }}
Symfony Events
Listen to the bundle’s events (e.g., page.save) in your EventSubscriber:
use ACSEO\PageBuilderBundle\Event\PageEvents;
public static function getSubscribedEvents()
{
return [
PageEvents::PRE_SAVE => 'onPreSave',
];
}
How can I help you explore Laravel packages today?