Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Phpagebuilder Laravel Package

hansschouten/phpagebuilder

PHPageBuilder is a drag-and-drop page builder for PHP applications. It lets you create and edit pages visually with customizable blocks, manage layouts, and store content in a database. Framework-agnostic, works with Laravel or plain PHP, with multilingual support and an extensible block system.

Deep Wiki
Context7

Getting Started

The package is a framework-agnostic drag-and-drop page builder. You define a schema (which containers/rows exist and which blocks can go in them), the editor UI lets an editor compose a page, and the result is stored as JSON that you render with plain PHP views.

  1. Install and pin. composer require hansschouten/phpagebuilder (if Packagist resolution fails, add the repo as a VCS entry in composer.json), then commit the lockfile — the last release is Jan 2025 and activity is thin, so treat the version as frozen until you deliberately upgrade.
  2. Look first at: the package's demo/ directory and the shipped pagebuilder.json (the schema file). That file is the real documentation: it shows containers (name + allowed block types) and blocks (name, view template path, and a fields definition used to auto-generate the editor form).
  3. Write your schema. Start with two containers (main, sidebar) and two blocks (text, image). Point each block's view at a partial in your app, e.g. resources/views/blocks/text.php, and give it one field (type: textarea / type: text).
  4. Wire an editor page. In a controller (or a plain PHP file), instantiate PhpPageBuilder::create($schemaPathOrArray). The editor is a single embedded page: the builder serves the Vue UI and handles the save POST for you. Put it behind auth before you show it to anyone.
  5. Render. On the public route, load the saved page JSON and call the builder's render path so it walks containers → blocks → your view partials. Your first use case is almost always a marketing/landing page: a hero text block, an image block, and a rich-text block in one container.

Minimal Laravel version: one route for GET /admin/pages/{page}/edit (builder), one for POST saves (if you take saving over yourself), one public GET /{slug} that renders the stored JSON.

Implementation Patterns

Schema as committed code, pages as data. Keep pagebuilder.json (or a config/pagebuilder.php array) in version control, one schema per site/locale/tenant. Pages — the editor output — live in storage. That separation is what makes schema changes reviewable and page content editable by non-developers.

Custom storage is the main integration seam. The default storage is file-based JSON, which is fine for a prototype but wrong for multi-server Laravel. Implement the package's storage interface against Eloquent: a pages table with slug, title, json, published_at. One row per page, JSON cast to array. This also gives you drafts vs. published, revisions (keep a page_revisions table on every save), and per-user permissions almost for free.

Block views are plain PHP includes — keep them dumb. Each partial should receive the block's saved field values and do nothing but escape and print. Typical block set for a real project: text (rich text), image (URL + alt + optional link), cta (label + href), embed (raw HTML, sanitized). Add a _wrapper block convention if you need per-block CSS classes/anchor IDs.

Render pipeline and caching. Don't call the renderer on every request. Compile once into HTML and cache it keyed on page_id + updated_at (Cache::remember). Render-time is cheap (includes), but it's still N file lookups and you lose the ability to cache the whole response otherwise. Invalidate on save and on deploy.

Reuse the editor for structured content, not just pages. Because the schema defines blocks independently, teams commonly point a second schema at a "sections" field on other models (product descriptions, landing sections on a CMS entry). Store the JSON in a json column and render it through the same partials.

Restrict rather than validate. The cleanest way to keep editors out of trouble is narrower containers: a hero container that only accepts text and image, a body container that accepts anything. Fewer schema variants beat runtime validation.

Laravel wiring. Bind a configured builder instance in a service provider built from config('pagebuilder.schema'); publish nothing, resolve the schema path from base_path(). Put editor routes in routes/admin.php behind auth + a policy, and the render route in routes/web.php. If you render inside Blade, expose a @pagebuilder($page) directive that calls your cached renderer and outputs {!! !!} — that's the one intentional unescaped output in the app, so keep it in one place.

Media. Field types cover text/textarea/select/image-URL; they do not manage uploads. Add your own upload endpoint (Laravel Storage::disk('public'), intervention/image for resizing) and have the image field store a path, resolved to a URL in the block partial so CDN/base-URL changes don't require re-editing every page.

Gotchas and Tips

  • No built-in auth or CSRF on the save endpoint. The builder predates your app's security model. Mount the editor only behind middleware, and if the package's own POST handler bypasses your CSRF stack, wrap saving in your own controller that validates the JSON and persists via your storage implementation. Never expose the editor route publicly "just to test".
  • File storage and deploys. Default JSON goes to a path inside the project. Atomic-deploy setups (Envoyer, Forge zero-downtime, Docker) will silently revert editors' work on each release if the storage dir is inside the release folder. Move it to storage/ or a DB before you go live, and check file permissions for the PHP-FPM user.
  • JSON is a schema contract — renaming a block type or field key breaks every saved page. Adopt the rule: additive changes only. If you must rename, add the new key, render both (fall back to old key in the partial), and migrate the stored JSON with a one-off command. Removed block types should render as an empty string, not a fatal — guard every partial with isset.
  • Untrusted HTML. Rich-text and embed blocks store editor HTML that gets printed raw. Run it through HTMLPurifier (or an allow-list sanitizer) at save time, not at render time — sanitizing on render costs you on cached pages and lets bad data persist. Escape every other field with e().
  • Debugging order of operations: dump the raw saved JSON first (is the block there at all?), then the schema (is the block type still allowed in that container?), then the partial path (relative to what? the package resolves view paths from its own root in some configurations — use absolute paths from base_path()/__DIR__ to remove ambiguity). Also log the POST body on save failures; the editor's error surface is thin.
  • Assets. The editor ships compiled JS/CSS. In a Laravel app they are not in public/ automatically; either symlink the package's dist directory at deploy or copy assets into your build. Pin the version — an unpinned upgrade can swap the build hash and leave you serving a stale UI that "works" but drops fields.
  • Low-activity dependency. ~874 stars, single-maintainer, infrequent releases. Budget for reading the source (it's small) and for the possibility of a fork. Keep the schema, the storage adapter, and the block partials as your code so that swapping the builder later is a week, not a rewrite.
  • Extension points worth knowing: custom storage class (biggest win), custom field types for the editor form, and your own renderer that wraps each block/container in classes — the last one lets you move from inline templates to Blade components without touching saved content.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core