<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Dipak Sarkar</title>
    <description>The latest articles on Forem by Dipak Sarkar (@dipaksarkar).</description>
    <link>https://forem.com/dipaksarkar</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2229066%2F58b8144d-d8d4-4731-8c96-9888087d2f50.png</url>
      <title>Forem: Dipak Sarkar</title>
      <link>https://forem.com/dipaksarkar</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/dipaksarkar"/>
    <language>en</language>
    <item>
      <title>Build a Visual Page Builder in Laravel Without Leaving Blade</title>
      <dc:creator>Dipak Sarkar</dc:creator>
      <pubDate>Wed, 18 Mar 2026 18:56:55 +0000</pubDate>
      <link>https://forem.com/dipaksarkar/build-a-visual-page-builder-in-laravel-without-leaving-blade-n5i</link>
      <guid>https://forem.com/dipaksarkar/build-a-visual-page-builder-in-laravel-without-leaving-blade-n5i</guid>
      <description>&lt;p&gt;If you've ever wanted a visual page editor inside your Laravel app — drag-and-drop sections, live preview, multi-theme support, all powered by plain Blade views — that's exactly what we built.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;coderstm/laravel-page-builder&lt;/code&gt;&lt;/strong&gt; is a JSON-driven page composition system for Laravel 11 and 12. It gives your users a visual editor while keeping your developer workflow exactly as it should be: Blade files, PHP classes, and zero magic.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;Most Laravel page builders fall into one of two traps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Too much magic&lt;/strong&gt; — custom DSLs, opaque rendering pipelines, or tight coupling to a specific frontend stack.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Too rigid&lt;/strong&gt; — pre-baked HTML structures that fight your design system the moment you need to deviate.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We wanted something that felt native to Laravel: Blade views for rendering, JSON for storage, and a clean PHP architecture underneath.&lt;/p&gt;




&lt;h2&gt;
  
  
  How It Works
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Sections and Blocks Are Just Blade Views
&lt;/h3&gt;

&lt;p&gt;A section is a Blade file that starts with a &lt;code&gt;@schema()&lt;/code&gt; directive. That's it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@schema([
    'name'     =&amp;gt; 'Hero',
    'settings' =&amp;gt; [
        ['id' =&amp;gt; 'title',    'type' =&amp;gt; 'text',  'label' =&amp;gt; 'Title',    'default' =&amp;gt; 'Welcome'],
        ['id' =&amp;gt; 'subtitle', 'type' =&amp;gt; 'text',  'label' =&amp;gt; 'Subtitle', 'default' =&amp;gt; 'Build something great'],
        ['id' =&amp;gt; 'bg_color', 'type' =&amp;gt; 'color', 'label' =&amp;gt; 'Background'],
    ],
])

&amp;lt;section {!! $section-&amp;gt;editorAttributes() !!} style="background: {{ $section-&amp;gt;settings-&amp;gt;bg_color }}"&amp;gt;
    &amp;lt;h1&amp;gt;{{ $section-&amp;gt;settings-&amp;gt;title }}&amp;lt;/h1&amp;gt;
    &amp;lt;p&amp;gt;{{ $section-&amp;gt;settings-&amp;gt;subtitle }}&amp;lt;/p&amp;gt;
&amp;lt;/section&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Drop this file in &lt;code&gt;resources/views/sections/hero.blade.php&lt;/code&gt; and it's immediately available in the editor. No registration, no YAML config, no artisan generate commands — the registry scans it automatically.&lt;/p&gt;

&lt;p&gt;Blocks follow the exact same pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@schema([
    'name'     =&amp;gt; 'Card',
    'settings' =&amp;gt; [
        ['id' =&amp;gt; 'title', 'type' =&amp;gt; 'text', 'label' =&amp;gt; 'Title', 'default' =&amp;gt; 'Card Title'],
        ['id' =&amp;gt; 'image', 'type' =&amp;gt; 'image', 'label' =&amp;gt; 'Image'],
    ],
])

&amp;lt;div {!! $block-&amp;gt;editorAttributes() !!}&amp;gt;
    &amp;lt;img src="{{ $block-&amp;gt;settings-&amp;gt;image }}" alt=""&amp;gt;
    &amp;lt;h3&amp;gt;{{ $block-&amp;gt;settings-&amp;gt;title }}&amp;lt;/h3&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Container sections accept blocks via &lt;code&gt;@blocks($section)&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;section {!! $section-&amp;gt;editorAttributes() !!}&amp;gt;
    @blocks($section)
&amp;lt;/section&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Pages Are Stored as JSON
&lt;/h3&gt;

&lt;p&gt;When an editor saves a page, it writes a JSON file to disk:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"sections"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"hero"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"hero"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"settings"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Welcome to Our Store"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"bg_color"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"#1a1a2e"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"features"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"feature-grid"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"settings"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"blocks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"card-1"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"card"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"settings"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Fast"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"card-2"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"card"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"settings"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Reliable"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"order"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"card-1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"card-2"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"order"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"hero"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"features"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No database tables for content. JSON on disk means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Version-controllable page content&lt;/li&gt;
&lt;li&gt;Fast reads with no query overhead&lt;/li&gt;
&lt;li&gt;Easy to seed, import, or diff&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The Rendering Pipeline
&lt;/h3&gt;

&lt;p&gt;The architecture follows a strict five-layer dependency flow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Schema → Registry → Components → Renderer → Services
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Schema&lt;/strong&gt; — Immutable &lt;code&gt;readonly&lt;/code&gt; value objects describing section/block structure&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Registry&lt;/strong&gt; — Singleton scanners that discover and cache schemas from Blade files&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Components&lt;/strong&gt; — Hydrated &lt;code&gt;Section&lt;/code&gt; and &lt;code&gt;Block&lt;/code&gt; runtime objects built from JSON + schema&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Renderer&lt;/strong&gt; — The single entry point for all HTML output; never bypassed&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Services&lt;/strong&gt; — High-level orchestrators (&lt;code&gt;PageService&lt;/code&gt;, &lt;code&gt;PageStorage&lt;/code&gt;, &lt;code&gt;PageRenderer&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Dependencies flow &lt;strong&gt;downward only&lt;/strong&gt;. A &lt;code&gt;Renderer&lt;/code&gt; never calls a &lt;code&gt;Service&lt;/code&gt;. A &lt;code&gt;Schema&lt;/code&gt; never calls a &lt;code&gt;Registry&lt;/code&gt;. This makes the codebase easy to trace and test.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Visual Editor
&lt;/h2&gt;

&lt;p&gt;The editor is a React SPA with an iframe live preview. Changes sync to the preview in real time — no page refresh, no manual save to see output.&lt;/p&gt;

&lt;p&gt;What the editor gives you out of the box:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Drag-and-drop&lt;/strong&gt; section and block reordering&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Settings panel&lt;/strong&gt; with 21+ field types: text, color, image, icon, rich text, select, toggle, range slider, and more&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inline text editing&lt;/strong&gt; directly on the preview&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Media library&lt;/strong&gt; with pluggable storage backends (local, S3, Cloudflare R2, DigitalOcean Spaces)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SEO panel&lt;/strong&gt; — title, description, keywords per page&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Theme settings&lt;/strong&gt; — global CSS variables (colors, fonts, spacing) editable in the sidebar&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The editor is injected via a single route and activates only when &lt;code&gt;?pb-editor=1&lt;/code&gt; is present. In production, &lt;code&gt;$section-&amp;gt;editorAttributes()&lt;/code&gt; returns an empty string — zero overhead.&lt;/p&gt;




&lt;h2&gt;
  
  
  JSON Templates — Fallbacks
&lt;/h2&gt;

&lt;p&gt;Not every page needs a custom layout. Templates let you define default structures for pages that don't have their own JSON or Blade view:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"layout"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"page"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"wrapper"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"main#page-main.container"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"sections"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"hero"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"hero"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"settings"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{{ $page-&amp;gt;title }}"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"page-content"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"order"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"hero"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Drop this in &lt;code&gt;resources/views/templates/default.json&lt;/code&gt;, assign &lt;code&gt;$page-&amp;gt;template = 'default'&lt;/code&gt;, and every page using that template renders with the same structure — settings pre-filled from the page model.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;wrapper&lt;/code&gt; key accepts a CSS selector: &lt;code&gt;main#id.class[attr=value]&lt;/code&gt;. The renderer builds the wrapping element from it automatically.&lt;/p&gt;




&lt;h2&gt;
  
  
  Multi-Theme Support
&lt;/h2&gt;

&lt;p&gt;The package integrates with &lt;code&gt;qirolab/laravel-themer&lt;/code&gt;. Each theme can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Register its own sections and blocks from &lt;code&gt;themes/{name}/views/sections/&lt;/code&gt; and &lt;code&gt;blocks/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Override app templates by shadowing &lt;code&gt;templates/{name}.json&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Define global settings schema (colors, fonts, spacing) that feed into CSS variables&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sections can accept any theme block using the &lt;code&gt;@theme&lt;/code&gt; wildcard:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@schema([
    'name'   =&amp;gt; 'Content Row',
    'blocks' =&amp;gt; [
        ['type' =&amp;gt; '@theme'],  // accepts any registered block from the active theme
    ],
])
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Themes can shadow without modifying. If a theme ships &lt;code&gt;hero.blade.php&lt;/code&gt;, it takes precedence over the app's &lt;code&gt;hero.blade.php&lt;/code&gt; — no changes to the app required.&lt;/p&gt;




&lt;h2&gt;
  
  
  Recursive Block Nesting
&lt;/h2&gt;

&lt;p&gt;Blocks nest to any depth. A &lt;code&gt;row&lt;/code&gt; block can contain &lt;code&gt;column&lt;/code&gt; blocks, which can contain &lt;code&gt;card&lt;/code&gt; blocks, which can contain &lt;code&gt;icon&lt;/code&gt; blocks. The renderer traverses the tree recursively, and the editor renders nested drag-and-drop handles at every level.&lt;/p&gt;

&lt;p&gt;Block references in a section schema work in three ways:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Entry&lt;/th&gt;
&lt;th&gt;Resolved as&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;['type' =&amp;gt; 'row']&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Theme block — looked up via &lt;code&gt;BlockRegistry&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;['type' =&amp;gt; '@theme']&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Wildcard — any registered block&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;['type' =&amp;gt; 'item', 'name' =&amp;gt; 'Custom Item']&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Local definition — used as-is&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The distinction matters: adding a &lt;code&gt;name&lt;/code&gt; key to a bare type reference changes it from a registry lookup to a local definition.&lt;/p&gt;




&lt;h2&gt;
  
  
  Per-Page Layout Zones
&lt;/h2&gt;

&lt;p&gt;Pages can define header and footer sections that render outside the main sortable content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"header"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"sections"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"nav"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"navigation"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"order"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"nav"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"sections"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"hero"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"hero"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"order"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"hero"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rendered via &lt;code&gt;@sections('header')&lt;/code&gt; in the Blade layout. Editors configure them in a dedicated panel — they don't appear in the main drag-and-drop list.&lt;/p&gt;




&lt;h2&gt;
  
  
  Adding a New Field Type
&lt;/h2&gt;

&lt;p&gt;The 21 built-in types cover most needs, but adding a custom type doesn't touch the core engine. Create a React component under &lt;code&gt;resources/js/components/settings/&lt;/code&gt;, register it in &lt;code&gt;FieldRegistry.ts&lt;/code&gt;, and it's available in any section's &lt;code&gt;@schema()&lt;/code&gt; definition.&lt;/p&gt;




&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;composer require coderstm/laravel-page-builder
php artisan vendor:publish &lt;span class="nt"&gt;--tag&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;pagebuilder-config
php artisan vendor:publish &lt;span class="nt"&gt;--tag&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;pagebuilder-views
php artisan migrate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then create your first section in &lt;code&gt;resources/views/sections/hero.blade.php&lt;/code&gt; and visit &lt;code&gt;/page-builder&lt;/code&gt; in your app.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;We're actively working on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Revision history&lt;/strong&gt; with diff view&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;More built-in section types&lt;/strong&gt; (pricing, testimonials, FAQ)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Headless mode&lt;/strong&gt; — JSON output for decoupled frontends&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GitHub&lt;/strong&gt;: &lt;a href="https://github.com/coders-tm/laravel-page-builder" rel="noopener noreferrer"&gt;github.com/coders-tm/laravel-page-builder&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Packagist&lt;/strong&gt;: &lt;code&gt;composer require coderstm/laravel-page-builder&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;If you've built something on top of this or have questions about the architecture, drop them in the comments. Happy to dig into any layer of the system.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>laravel</category>
      <category>php</category>
    </item>
    <item>
      <title>Build a Visual Page Builder in Laravel Without Leaving Blade</title>
      <dc:creator>Dipak Sarkar</dc:creator>
      <pubDate>Wed, 18 Mar 2026 18:56:55 +0000</pubDate>
      <link>https://forem.com/dipaksarkar/build-a-visual-page-builder-in-laravel-without-leaving-blade-59fo</link>
      <guid>https://forem.com/dipaksarkar/build-a-visual-page-builder-in-laravel-without-leaving-blade-59fo</guid>
      <description>&lt;p&gt;If you've ever wanted a Shopify-style page editor inside your Laravel app — drag-and-drop sections, live preview, multi-theme support, all powered by plain Blade views — that's exactly what we built.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;coderstm/laravel-page-builder&lt;/code&gt;&lt;/strong&gt; is a JSON-driven page composition system for Laravel 11 and 12. It gives your users a visual editor while keeping your developer workflow exactly as it should be: Blade files, PHP classes, and zero magic.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;Most Laravel page builders fall into one of two traps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Too much magic&lt;/strong&gt; — custom DSLs, opaque rendering pipelines, or tight coupling to a specific frontend stack.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Too rigid&lt;/strong&gt; — pre-baked HTML structures that fight your design system the moment you need to deviate.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We wanted something that felt native to Laravel: Blade views for rendering, JSON for storage, and a clean PHP architecture underneath.&lt;/p&gt;




&lt;h2&gt;
  
  
  How It Works
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Sections and Blocks Are Just Blade Views
&lt;/h3&gt;

&lt;p&gt;A section is a Blade file that starts with a &lt;code&gt;@schema()&lt;/code&gt; directive. That's it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@schema([
    'name'     =&amp;gt; 'Hero',
    'settings' =&amp;gt; [
        ['id' =&amp;gt; 'title',    'type' =&amp;gt; 'text',  'label' =&amp;gt; 'Title',    'default' =&amp;gt; 'Welcome'],
        ['id' =&amp;gt; 'subtitle', 'type' =&amp;gt; 'text',  'label' =&amp;gt; 'Subtitle', 'default' =&amp;gt; 'Build something great'],
        ['id' =&amp;gt; 'bg_color', 'type' =&amp;gt; 'color', 'label' =&amp;gt; 'Background'],
    ],
])

&amp;lt;section {!! $section-&amp;gt;editorAttributes() !!} style="background: {{ $section-&amp;gt;settings-&amp;gt;bg_color }}"&amp;gt;
    &amp;lt;h1&amp;gt;{{ $section-&amp;gt;settings-&amp;gt;title }}&amp;lt;/h1&amp;gt;
    &amp;lt;p&amp;gt;{{ $section-&amp;gt;settings-&amp;gt;subtitle }}&amp;lt;/p&amp;gt;
&amp;lt;/section&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Drop this file in &lt;code&gt;resources/views/sections/hero.blade.php&lt;/code&gt; and it's immediately available in the editor. No registration, no YAML config, no artisan generate commands — the registry scans it automatically.&lt;/p&gt;

&lt;p&gt;Blocks follow the exact same pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@schema([
    'name'     =&amp;gt; 'Card',
    'settings' =&amp;gt; [
        ['id' =&amp;gt; 'title', 'type' =&amp;gt; 'text', 'label' =&amp;gt; 'Title', 'default' =&amp;gt; 'Card Title'],
        ['id' =&amp;gt; 'image', 'type' =&amp;gt; 'image', 'label' =&amp;gt; 'Image'],
    ],
])

&amp;lt;div {!! $block-&amp;gt;editorAttributes() !!}&amp;gt;
    &amp;lt;img src="{{ $block-&amp;gt;settings-&amp;gt;image }}" alt=""&amp;gt;
    &amp;lt;h3&amp;gt;{{ $block-&amp;gt;settings-&amp;gt;title }}&amp;lt;/h3&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Container sections accept blocks via &lt;code&gt;@blocks($section)&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;section {!! $section-&amp;gt;editorAttributes() !!}&amp;gt;
    @blocks($section)
&amp;lt;/section&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Pages Are Stored as JSON
&lt;/h3&gt;

&lt;p&gt;When an editor saves a page, it writes a JSON file to disk:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"sections"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"hero"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"hero"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"settings"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Welcome to Our Store"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"bg_color"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"#1a1a2e"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"features"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"feature-grid"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"settings"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"blocks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"card-1"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"card"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"settings"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Fast"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"card-2"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"card"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"settings"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Reliable"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"order"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"card-1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"card-2"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"order"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"hero"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"features"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No database tables for content. JSON on disk means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Version-controllable page content&lt;/li&gt;
&lt;li&gt;Fast reads with no query overhead&lt;/li&gt;
&lt;li&gt;Easy to seed, import, or diff&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The Rendering Pipeline
&lt;/h3&gt;

&lt;p&gt;The architecture follows a strict five-layer dependency flow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Schema → Registry → Components → Renderer → Services
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Schema&lt;/strong&gt; — Immutable &lt;code&gt;readonly&lt;/code&gt; value objects describing section/block structure&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Registry&lt;/strong&gt; — Singleton scanners that discover and cache schemas from Blade files&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Components&lt;/strong&gt; — Hydrated &lt;code&gt;Section&lt;/code&gt; and &lt;code&gt;Block&lt;/code&gt; runtime objects built from JSON + schema&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Renderer&lt;/strong&gt; — The single entry point for all HTML output; never bypassed&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Services&lt;/strong&gt; — High-level orchestrators (&lt;code&gt;PageService&lt;/code&gt;, &lt;code&gt;PageStorage&lt;/code&gt;, &lt;code&gt;PageRenderer&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Dependencies flow &lt;strong&gt;downward only&lt;/strong&gt;. A &lt;code&gt;Renderer&lt;/code&gt; never calls a &lt;code&gt;Service&lt;/code&gt;. A &lt;code&gt;Schema&lt;/code&gt; never calls a &lt;code&gt;Registry&lt;/code&gt;. This makes the codebase easy to trace and test.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Visual Editor
&lt;/h2&gt;

&lt;p&gt;The editor is a React SPA with an iframe live preview. Changes sync to the preview in real time — no page refresh, no manual save to see output.&lt;/p&gt;

&lt;p&gt;What the editor gives you out of the box:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Drag-and-drop&lt;/strong&gt; section and block reordering&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Settings panel&lt;/strong&gt; with 21+ field types: text, color, image, icon, rich text, select, toggle, range slider, and more&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inline text editing&lt;/strong&gt; directly on the preview&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Media library&lt;/strong&gt; with pluggable storage backends (local, S3, Cloudflare R2, DigitalOcean Spaces)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SEO panel&lt;/strong&gt; — title, description, keywords per page&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Theme settings&lt;/strong&gt; — global CSS variables (colors, fonts, spacing) editable in the sidebar&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The editor is injected via a single route and activates only when &lt;code&gt;?pb-editor=1&lt;/code&gt; is present. In production, &lt;code&gt;$section-&amp;gt;editorAttributes()&lt;/code&gt; returns an empty string — zero overhead.&lt;/p&gt;




&lt;h2&gt;
  
  
  JSON Templates — Shopify-Style Fallbacks
&lt;/h2&gt;

&lt;p&gt;Not every page needs a custom layout. Templates let you define default structures for pages that don't have their own JSON or Blade view:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"layout"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"page"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"wrapper"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"main#page-main.container"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"sections"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"hero"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"hero"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"settings"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{{ $page-&amp;gt;title }}"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"page-content"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"order"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"hero"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Drop this in &lt;code&gt;resources/views/templates/default.json&lt;/code&gt;, assign &lt;code&gt;$page-&amp;gt;template = 'default'&lt;/code&gt;, and every page using that template renders with the same structure — settings pre-filled from the page model.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;wrapper&lt;/code&gt; key accepts a CSS selector: &lt;code&gt;main#id.class[attr=value]&lt;/code&gt;. The renderer builds the wrapping element from it automatically.&lt;/p&gt;




&lt;h2&gt;
  
  
  Multi-Theme Support
&lt;/h2&gt;

&lt;p&gt;The package integrates with &lt;code&gt;qirolab/laravel-themer&lt;/code&gt;. Each theme can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Register its own sections and blocks from &lt;code&gt;themes/{name}/views/sections/&lt;/code&gt; and &lt;code&gt;blocks/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Override app templates by shadowing &lt;code&gt;templates/{name}.json&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Define global settings schema (colors, fonts, spacing) that feed into CSS variables&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sections can accept any theme block using the &lt;code&gt;@theme&lt;/code&gt; wildcard:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@schema([
    'name'   =&amp;gt; 'Content Row',
    'blocks' =&amp;gt; [
        ['type' =&amp;gt; '@theme'],  // accepts any registered block from the active theme
    ],
])
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Themes can shadow without modifying. If a theme ships &lt;code&gt;hero.blade.php&lt;/code&gt;, it takes precedence over the app's &lt;code&gt;hero.blade.php&lt;/code&gt; — no changes to the app required.&lt;/p&gt;




&lt;h2&gt;
  
  
  Recursive Block Nesting
&lt;/h2&gt;

&lt;p&gt;Blocks nest to any depth. A &lt;code&gt;row&lt;/code&gt; block can contain &lt;code&gt;column&lt;/code&gt; blocks, which can contain &lt;code&gt;card&lt;/code&gt; blocks, which can contain &lt;code&gt;icon&lt;/code&gt; blocks. The renderer traverses the tree recursively, and the editor renders nested drag-and-drop handles at every level.&lt;/p&gt;

&lt;p&gt;Block references in a section schema work in three ways:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Entry&lt;/th&gt;
&lt;th&gt;Resolved as&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;['type' =&amp;gt; 'row']&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Theme block — looked up via &lt;code&gt;BlockRegistry&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;['type' =&amp;gt; '@theme']&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Wildcard — any registered block&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;['type' =&amp;gt; 'item', 'name' =&amp;gt; 'Custom Item']&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Local definition — used as-is&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The distinction matters: adding a &lt;code&gt;name&lt;/code&gt; key to a bare type reference changes it from a registry lookup to a local definition.&lt;/p&gt;




&lt;h2&gt;
  
  
  Per-Page Layout Zones
&lt;/h2&gt;

&lt;p&gt;Pages can define header and footer sections that render outside the main sortable content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"header"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"sections"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"nav"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"navigation"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"order"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"nav"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"sections"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"hero"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"hero"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"order"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"hero"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rendered via &lt;code&gt;@sections('header')&lt;/code&gt; in the Blade layout. Editors configure them in a dedicated panel — they don't appear in the main drag-and-drop list.&lt;/p&gt;




&lt;h2&gt;
  
  
  Adding a New Field Type
&lt;/h2&gt;

&lt;p&gt;The 21 built-in types cover most needs, but adding a custom type doesn't touch the core engine. Create a React component under &lt;code&gt;resources/js/components/settings/&lt;/code&gt;, register it in &lt;code&gt;FieldRegistry.ts&lt;/code&gt;, and it's available in any section's &lt;code&gt;@schema()&lt;/code&gt; definition.&lt;/p&gt;




&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;composer require coderstm/laravel-page-builder
php artisan vendor:publish &lt;span class="nt"&gt;--tag&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;pagebuilder-config
php artisan vendor:publish &lt;span class="nt"&gt;--tag&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;pagebuilder-views
php artisan migrate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then create your first section in &lt;code&gt;resources/views/sections/hero.blade.php&lt;/code&gt; and visit &lt;code&gt;/page-builder&lt;/code&gt; in your app.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;We're actively working on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Revision history&lt;/strong&gt; with diff view&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;More built-in section types&lt;/strong&gt; (pricing, testimonials, FAQ)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Headless mode&lt;/strong&gt; — JSON output for decoupled frontends&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GitHub&lt;/strong&gt;: &lt;a href="https://github.com/coders-tm/laravel-page-builder" rel="noopener noreferrer"&gt;github.com/coders-tm/laravel-page-builder&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Packagist&lt;/strong&gt;: &lt;code&gt;composer require coderstm/laravel-page-builder&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;If you've built something on top of this or have questions about the architecture, drop them in the comments. Happy to dig into any layer of the system.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>laravel</category>
      <category>php</category>
    </item>
  </channel>
</rss>
