CMS Framework - v2.2.2
Site Editor - Visual Editor Integration
cms-framework's site-editor surface is consumed by the optional artisanpack-ui/visual-editor package, but is not dependent on it. visual-editor remains an opt-in integration rather than a hard composer requirement — apps that don't install it still get the REST endpoints and the resolvers, just without the editor UI on top.
Added in 2.0.0.
The compatibility shim
cms-framework's Post and Page models (and BlockPattern, Template, TemplatePart) use ArtisanPackUI\VisualEditor\Concerns\HasBlockContent;. When artisanpack-ui/visual-editor is installed, that trait is autoloaded from the visual-editor package and provides the full editor surface (search extractor, query scope, render pipeline, etc.).
When visual-editor is not installed, cms-framework's src/Compatibility/visual-editor.php stub registers a minimal version of the trait. The stub:
- Casts the
block_contentcolumn toarrayso model round-trips work. - Exposes
getBlockContentColumn()for the default column name (block_content).
The stub is loaded via Composer's autoload.files. It checks trait_exists(__NAMESPACE__.'\\HasBlockContent', true) before declaring — when visual-editor is installed, the real trait is autoloaded via PSR-4 and the stub is skipped.
This means:
- Models that adopt
HasBlockContentload cleanly with or without visual-editor. - Apps that need the editor surface install
artisanpack-ui/visual-editorand get the full trait. - Apps that only need the REST API and resolvers don't need visual-editor at all.
The block_content column
cms-framework's 2026_04_28_000001_add_block_content_to_posts_table.php and _to_pages_table.php migrations add a JSON block_content column to posts and pages, alongside the existing content longText column. The two columns are independent:
contentcarries the legacy HTML body (rendered output, sometimes hand-edited).block_contentcarries the parsed block tree (array of block descriptors) for visual-editor-authored content.
block_content is not in the $fillable array on Post or Page — block trees flow through the visual-editor pipeline, not mass assignment. Direct calls to $post->block_content = [...]; $post->save(); still work.
Filter registration
When visual-editor is installed, SiteEditorServiceProvider registers cms-framework's resolvers against the ap.visual-editor.* filter family:
| Filter | What it provides |
|---|---|
ap.visual-editor.templates |
Map of resolved templates from TemplateResolver |
ap.visual-editor.template-parts |
Map of resolved template parts from TemplatePartResolver |
ap.visual-editor.patterns |
Map of resolved patterns (theme + user) from PatternResolver |
ap.visual-editor.global-styles |
Singleton resolved global styles from GlobalStylesResolver |
ap.visual-editor.navigation |
Map of resolved menus by location from MenuResolver |
ap.visual-editor.resources |
Auto-registers content types that adopt HasBlockContent |
All registrations are gated behind class_exists(VisualEditor::class) so cms-framework boots cleanly when visual-editor isn't installed.
Permissions
When visual-editor is installed, cms-framework seeds the following permission slugs via the parent CMSFrameworkServiceProvider:
visual_editor.templates.editvisual_editor.template-parts.editvisual_editor.patterns.editvisual_editor.global-styles.editvisual_editor.navigation.edit
Routes themselves use auth:sanctum for the 2.0.0 baseline; future releases will introduce policy-based gating on these slugs.
Content type auto-registration
Any Eloquent model that adopts HasBlockContent is automatically registered into the ap.visual-editor.resources filter via cms-framework's bridge code. Post and Page are registered explicitly; custom content types that adopt the trait are picked up at boot.
Schema guards
All ap.visual-editor.* filter callbacks are schema-guarded — they skip resolution and return the existing filter value early when the underlying tables are missing. This lets the package boot cleanly in environments where the migrations haven't run yet (CI bootstraps, fresh installs).