Skip to content

The visual builder

The visual builder is a full-screen GrapesJS drag-drop editor for building pages without writing markup. It opens at /cms/page/design for an existing page (the admin shell is disabled so the builder owns the viewport); saving goes through the /api service (Cms_Service_Page::saveDesign), never a page-POST.

A builder page uses the builder body format — a safe format: it's rendered like html (with the shortcode pass), and any <script> is stripped on save, so a tenant can design freely without introducing code.

How builder content is stored

When you save a design, the builder posts the rendered HTML plus its scoped CSS. The service stores a self-contained body:

<style> …scoped css… </style>
…rendered markup…

into page.body with format = builder. That body is the source of truth — it's a complete, portable snapshot that renders on its own. Reopening the builder re-seeds the canvas straight from this HTML; each component re-parses its own state from the markup.

Earlier versions also stored a GrapesJS project JSON blob in meta.builder. That was dropped: a shadow copy drifts from the body markup (the drift corrupted widget components), so the HTML body is now the single source of truth and the builder rehydrates from it. (meta.builder is stripped on save; the rest of meta — SEO, head_html — is preserved.)

Page metadata — title, slug, status, published_at, SEO — is edited in the normal page editor, not the builder; the builder touches the design only.

Widgets: dynamic references, not frozen markup

Some builder blocks represent dynamic content. They preview live in the canvas but export a shortcode, so the design stays dynamic and auth-filtered at view time:

  • Menu widget — previews a live menu in the canvas, exports .
  • Partial widget — previews a rendered partial, exports . Edit the partial once and every page that dropped it updates.

These ride the shortcode registry (see Shortcodes): the builder saves the reference, and it's resolved fresh on every render.

Blocks — copy-in fragments

A block (type = block) is the builder's reusable-fragment library placed by copy: dropping a block inlines its editable HTML into the page, detached from the source. That's the opposite of a partial (a live reference). A block is a builder library source only — there's no [block] shortcode and it's never resolved at render time. Save a selection as a block (Cms_Service_Page::saveBlock) and it appears in the builder's "My Blocks" palette for reuse.

The opaque-widget component pattern

Complex, interactive widgets (sliders, marquees, a theme's bespoke components) follow one rule: the component generates its own HTML, and the builder just saves it. The component owns its markup generation; GrapesJS never round-trips its internals. On reopen the component re-parses its state from the saved HTML rather than from a stored project graph.

This is what keeps builder content portable and drift-free — the body HTML is authoritative, and every component knows how to reconstruct itself from it. A theme can ship its own editable component types by declaring a builderJs file in its manifest (loaded before the canvas seeds); its components/*.phtml blocks and canvas CSS are passed to the builder so they preview in the theme's own style. See Theming for the theme side of this contract.

See also

  • Pages — the builder format alongside html/markdown/phtml
  • Layouts & partials — partials (reference) vs blocks (copy)
  • Shortcodes — what the menu and partial widgets export
  • Versioning — every builder save snapshots a version
  • Theming — theme-supplied builder components