A Gutenberg-powered visual editor for Laravel — post editor, site editor, and Blade/React/Vue block renderers.
A Gutenberg-powered visual editor for Laravel applications. Brings the WordPress block editor to any Eloquent model, plus a full site editor for templates, template parts, global styles, navigation menus, and patterns.
V1.0 ships:
HasBlockContent trait./visual-editor/site.artisanpack/* namespace.
core/* markup pasted from upstream auto-converts on insert.artisanpack/icon, v1.1) — Font Awesome 6 Free
bundled, custom SVG paste with server-side sanitization, and admin
upload for licensed sets (e.g. Font Awesome Pro). See
docs/blocks/Icon-Block.md.artisanpack-ui/cms-framework
for Posts, Pages, site-meta, navigation, and global-styles persistence.
Both packages remain usable standalone.Full documentation: see docs/. Start with Quick Start, or browse the documentation home for the full surface.
composer require artisanpack-ui/visual-editor php artisan migrate
For the site editor (recommended for most apps):
composer require artisanpack-ui/cms-framework php artisan migrate
See the Installation Guide and Quick Start for the full setup.
Version compatibilityThe visual-editor and artisanpack-ui/cms-framework packages ship as a
version pair — both packages need to be present, and both need to be on
a compatible major version, for the site-editor integration to work.
Install one without the other and the site-editor's install gate (#432)
surfaces a "cms-framework required" page instead of mounting.
| visual-editor | cms-framework | Notes |
|---|---|---|
| v1.x | v1.x | Site-editor integration (this release) |
| v0.x | v0.x | Pre-v1 — no site-editor integration |
Bumping the major on either package without bumping the partner is unsupported.
Peer dependenciesThe editor UI is built on @artisanpack-ui/react,
which is styled with DaisyUI and Tailwind CSS. Host applications
embedding the editor must have the following installed and loaded:
tailwindcss ^4.0.0daisyui ^5.0.0Add the HasBlockContent trait to any model and register it in
config/artisanpack/visual-editor.php:
use ArtisanPackUI\VisualEditor\Concerns\HasBlockContent; class Post extends Model { use HasBlockContent; }
// config/artisanpack/visual-editor.php return [ 'resources' => [ 'posts' => App\Models\Post::class, ], ];
Mount the editor in a Blade view:
<x-visual-editor :model="$post" />
The site editor mounts automatically at /visual-editor/site when
cms-framework is installed and the configured SiteEditorAccessGate
permits the request.
See docs/post-editor/Blade-Component.md for the full
component reference and docs/site-editor.md for
the site-editor surface.
| Topic | Doc |
|---|---|
HasBlockContent + resource map |
docs/content-model.md |
| Blade / React / Vue renderers | docs/renderers.md |
| Filters, actions, browser events | docs/Hooks-and-Events.md |
| Migration & WP import | docs/migration.md |
| Common problems | docs/troubleshooting.md |
| Topic | Doc |
|---|---|
| Surface tour | docs/post-editor.md |
| Quick start | docs/post-editor/Getting-Started.md |
<x-visual-editor /> reference |
docs/post-editor/Blade-Component.md |
| Embedding in Livewire | docs/post-editor/Livewire-Integration.md |
| Embedding in Inertia (React/Vue) | docs/post-editor/Inertia-Integration.md |
| Theming editor chrome | docs/post-editor/Theming.md |
| Topic | Doc |
|---|---|
| Surface tour | docs/site-editor.md |
| Quick start | docs/site-editor/Getting-Started.md |
SiteEditorAccessGate contract |
docs/site-editor/Access-Gate.md |
| Template hierarchy + parts | docs/site-editor/Templates.md |
| theme.json-backed global styles | docs/site-editor/Global-Styles.md |
| Menus, locations, fallbacks | docs/site-editor/Navigation.md |
| Synced vs unsynced patterns | docs/site-editor/Patterns.md |
| Topic | Doc |
|---|---|
| Block library overview | docs/blocks.md |
| Authoring custom blocks | docs/blocks/Custom-Blocks.md |
| Per-breakpoint values | docs/blocks/Responsive-Design-Tools.md |
| Per-state overrides | docs/blocks/State-Design-Tools.md |
The V1 editor adopts the upstream @wordpress/* packages. Some of those
packages expect a WordPress backend that this package doesn't provide,
so we ship temporary shims under resources/js/visual-editor/vendor/:
core-data-shim.ts — aliased in vite.config.ts as
@wordpress/core-data. Provides the entity registrations and
selectors Gutenberg expects (getEntityRecord, getEntityRecords,
resolvers, permissions stubs). Templates, template parts, navigation,
patterns, global styles, attachments, and site-meta are wired through
to the package's REST surface.media-upload-stub.tsx — registers editor.MediaUpload via
@wordpress/hooks so the media-library picker on core/image is
routed through a stub or the real
registerArtisanpackMediaBridge(MediaModal, uploadMedia) bridge.Both shims will be replaced over the V1.x release line as the cms-framework
side surfaces real backings. Every selector or filter implemented here is
one to re-verify against Gutenberg upgrades. See
docs/troubleshooting.md.
V1 ships a frozen allow-list of forked blocks under the artisanpack/*
namespace. The defaults in config/artisanpack/visual-editor.php expose every block
that landed during the Phase I block fork — @wordpress/block-library's
registerCoreBlocks() is no longer called, and the editor registers
only the in-package forks discovered under
resources/js/visual-editor/blocks/. See docs/blocks.md
for the full block library overview and the core/* → artisanpack/*
mapping.
The forked allow-list covers the content, media, layout, widget, entity,
loop/feed, comments, query/pagination, and authentication clusters.
Entity blocks (artisanpack/post-*, artisanpack/site-*,
artisanpack/template-part, artisanpack/navigation) and the loop /
feed cluster (artisanpack/query, artisanpack/post-template,
artisanpack/archives, artisanpack/categories,
artisanpack/tag-cloud) need an entity in scope to render meaningful
content — pair the editor with
artisanpack-ui/cms-framework
and they resolve against Posts / Pages / templates / site settings
end-to-end. Standalone, they fall back to empty shells rather than
crashing.
disabled_blocks is empty by default: with the I7 cutover (#415) the
editor no longer registers any core/* block, so there's nothing to
deny-list. New @wordpress/block-library releases similarly bring no
new registrations into this package — additions land only when a fork
is added to the in-package blocks directory and to the allow-list.
from:core/* transforms ship on each fork so existing core/* markup
pasted from upstream converts on insert.
Override the defaults by publishing the config to
config/artisanpack/visual-editor.php and editing the enabled_blocks
/ disabled_blocks arrays. The deny-list always wins over the allow-list.
The visual editor is fully usable standalone, but pairs with
artisanpack-ui/cms-framework
to unlock:
Post and Page content out of the box.core/site-* blocks (site title, tagline, logo, icon).core/post-*, core/query, taxonomy widget, and navigation
blocks.visual_editor.* permissions.composer require artisanpack-ui/visual-editor artisanpack-ui/cms-framework php artisan migrate
Both packages are loosely coupled — cms-framework's editor wiring is
guarded by class_exists(\ArtisanPackUI\VisualEditor\VisualEditor::class),
so each remains usable on its own. See
docs/site-editor/Getting-Started.md
for the site-editor pairing walkthrough.
Hook rename (v1.5.0, #664). Every visual-editor hook now uses camelCase — the old kebab-case names below (
ap.visual-editor.*,ap.icons.register-icon-sets,visual_editor.pre_publish_checks) remain functional throughdeprecateHook()aliases and continue to route callbacks to the canonical name, but log a deprecation notice the first time each alias resolves per process. New code should use the camelCase names. Seesrc/Support/HookAliases.phpfor the full rename table.
ap.visualEditor.resourcesRegister slug → Eloquent model class mappings used by
/visual-editor/api/{resource}/{id}/content. Filter contributions are
merged with config('artisanpack.visual-editor.resources'); the static
config wins on key collision so host-app overrides always take
precedence. Models must use
ArtisanPackUI\VisualEditor\Concerns\HasBlockContent — invalid entries
surface as InvalidArgumentException on first request rather than at
boot, so a contributor's standalone install never trips host boot.
addFilter('ap.visualEditor.resources', function (array $resources): array { return array_merge([ 'posts' => App\Models\Post::class, ], $resources); });
Full contract: docs/content-model.md and
docs/Hooks-and-Events.md.
ap.icons.registerIconSetsEditor chrome icons resolve through artisanpack-ui/icons. Register
additional icon sets in a service provider — see the
artisanpack-ui/icons docs.
Six additional hooks cover the block-registration, block-render,
post-persistence, editor-config, and pattern-render lifecycles. The
tables below summarise each; the full contract is documented in
docs/Hooks-and-Events.md.
| Hook | Type | Fired at | Payload |
|---|---|---|---|
ap.visualEditor.blockRegistered |
action | BlockTypeRegistry::register() end |
(string $name, array $config) |
ap.visualEditor.beforeRender |
filter | BlockRenderer::renderBlock() pre-render |
(array $attributes, string $name) |
ap.visualEditor.postSaved |
action | WpEntityController::persistNew() / persistUpdate() after save |
(int|string $postId, array $blocks) |
ap.visualEditor.postPublished |
action | same site, only on non-publish → publish transitions |
(int|string $postId, array $blocks) |
ap.visualEditor.editorConfig |
filter | VisualEditorComponent::__construct() config assembly |
(array $config, string $screen) |
ap.visualEditor.patternRender |
filter | PatternAdapter::toArray() on content.raw |
(string $html, string $slug, array $context) |
The visual editor ships five optional AI-powered authoring affordances
that build on top of the artisanpack-ui/ai foundation. All
five default to off — hosts opt in by enabling the corresponding
feature key from the AI package's settings surface, and each affordance
honors that toggle before rendering.
| Feature key | Agent | What it does |
|---|---|---|
visual_editor.suggest_next_block |
ContentBlockSuggestionAgent |
Inline "+ suggest" affordance ranks likely next blocks given the document so far. |
visual_editor.suggest_layout |
LayoutSuggestionAgent |
Given a section's content + your available pattern library, ranks matching section patterns. |
visual_editor.heading_hierarchy |
HeadingHierarchyAgent |
Audits the document for skipped levels, duplicate h1s, and ambiguous headings. |
ai.alt_text |
AltTextGenerationAgent (from ai) |
Suggests accessibility-friendly alt text when an image block is added or its src changes and alt is empty. |
ai.content_rewrite |
ContentRewriteAgent (from ai) |
Selection-toolbar / slash-command surface for "make shorter", "more formal", "reading level 6", etc. |
The first three agents live in this package (src/Ai/Agents/); the last
two are cross-cutting agents consumed directly from artisanpack-ui/ai
so the same prompt + feature toggle powers alt-text and content rewrites
across every package that opts in.
The features are auto-registered with the AI package's FeatureRegistry
via VisualEditorServiceProvider::aiFeatures() — no manual wiring
required. Each has a JSON endpoint under /visual-editor/api/ai/* for
the React editor to hit:
GET /visual-editor/api/ai/features # { features: { <key>: bool, ... } }
POST /visual-editor/api/ai/suggest-next-block
POST /visual-editor/api/ai/suggest-layout
POST /visual-editor/api/ai/alt-text
POST /visual-editor/api/ai/rewrite
POST /visual-editor/api/ai/heading-hierarchy
Blade / Livewire hosts can consume the same triggers through the shipped
Livewire component artisanpack-visual-editor.ai.tools, which listens
for ap-ve-ai:* browser events and re-dispatches results as
ap-ve-ai:{feature}:{status}.
import { createAiApiClient, useAiFeatures, SuggestNextBlockButton, HeadingHierarchyPanel, } from '../visual-editor/ai'; const client = createAiApiClient({ apiBase: '/visual-editor/api' }); const { isEnabled } = useAiFeatures(client); {isEnabled('visual_editor.suggest_next_block') && ( <SuggestNextBlockButton client={client} existingBlocks={blocks} cursorPosition={insertionIndex} onPick={(suggestion) => insertBlock(suggestion.block_type)} /> )}
Full component list: resources/js/visual-editor/ai/index.ts.
artisanpack-ui/ai ^1.0 installed and configured with credentials./visual-editor/api/* route group so
the shipped JS client's X-CSRF-TOKEN header is honored.Every affordance surfaces as a suggestion — never an automatic mutation. Accepting a suggestion is always explicit user action, per the AI RFC.
i18nEditor strings use @wordpress/i18n with the artisanpack-visual-editor
text domain. The domain is initialized via bootI18n() in
resources/js/visual-editor/vendor/i18n.ts.
Regenerate the placeholder .pot catalog with:
npm run i18n:extract
The extractor scans resources/js/visual-editor/**/*.{ts,tsx} for
__/_x/_n/_nx calls bound to the text domain and writes
languages/artisanpack-visual-editor.pot.
As an open source project, this package is open to contributions from anyone. Please read through the contributing guidelines to learn more about how you can contribute to this project.
| # | Наименование новости | Тональность | Информативность | Дата публикации |
|---|---|---|---|---|
| 1 | laravel-workflow/waterline (2.0.0-rc.1) | 0 | 21.11 | 28-07-2026 |
| 2 | laravel-workflow/laravel-workflow (2.0.0-rc.1) | 0 | 18.33 | 28-07-2026 |
| 3 | durable-workflow/waterline (2.0.0-rc.1) | 0 | 21.11 | 28-07-2026 |
| 4 | shieldci/laravel (v1.12.5) | 0 | 8.86 | 28-07-2026 |
| 5 | blendbyte/filament-title-with-slug (v3.1.0) | 0 | 21.76 | 28-07-2026 |
| 6 | ivanharmat/crewgrid (v0.2.0) | 0 | 19.86 | 28-07-2026 |
| 7 | picodev 0.1.5 | 0 | 5 | 10-07-2026 |
| 8 | moaines/illumi-search (1.21.4) | 0 | 37.46 | 28-07-2026 |
| 9 | milpa/live (v0.2.0) | 0 | 17.79 | 28-07-2026 |
| 10 | picodev 0.1.4 | 0 | 5 | 10-07-2026 |