coderstm / laravel-page-builder
A section-based page builder for Laravel using JSON layouts, sections, blocks, and themes.
Package info
github.com/coders-tm/laravel-page-builder
Language:TypeScript
pkg:composer/coderstm/laravel-page-builder
Requires
- php: ^8.2
- illuminate/http: ^11.0|^12.0
- illuminate/routing: ^11.0|^12.0
- illuminate/support: ^11.0|^12.0
- illuminate/view: ^11.0|^12.0
- qirolab/laravel-themer: ^2.4
- spatie/laravel-sluggable: ^3.6
Requires (Dev)
- larastan/larastan: ^2.9|^3.0
- laravel/pint: ^1.0
- orchestra/testbench: ^9.0|^10.0
- pestphp/pest: ^3.0|^4.0
- pestphp/pest-plugin-laravel: ^3.0|^4.1
- phpstan/phpstan: ^2.1
- phpunit/phpunit: ^11.0|^12.0
README
A modern page builder for Laravel that allows you to build dynamic pages using layouts, sections and JSON rendering. It includes a visual editor, layout system, reusable sections and multi-theme support.
Features
- Blade-native rendering — sections and blocks are regular Blade views with typed PHP objects
@schema()directive — declare settings, child blocks, and presets directly in Blade templates- Visual editor — React SPA with iframe live preview, drag-and-drop, and inline text editing
- JSON-based storage — page data stored as JSON files on disk for fast reads and easy version control
- JSON templates — fallback layouts for pages without a per-page JSON; supports
wrapper, variable interpolation ({{ $page->title }}), and theme overrides - Per-page Layouts — site header and footer are configurable per-page, stored in the page JSON
- Recursive block nesting — container blocks (rows, columns) can hold child blocks to any depth
- Theme blocks — register global block types that any section can accept via
@themewildcard - 21+ Field Types — from basic text inputs to advanced color pickers, icon selectors, and custom types
- Page Meta Persistence — SEO titles and descriptions are automatically managed and persisted across dynamic and preserved pages
- Editor mode —
data-editor-*attributes injected only when the editor is active - Publishable assets — config, views, migrations, and frontend assets can be published independently
Requirements
- PHP 8.2+
- Laravel 11.x or 12.x
Installation
composer require coderstm/laravel-page-builder
The package auto-registers its service provider via Laravel's package discovery.
Run the install command
php artisan pagebuilder:install
This single command:
- Publishes
config/pagebuilder.php - Publishes database migrations
- Publishes the compiled editor frontend assets to
public/pagebuilder/ - Scaffolds default starter views into your app:
resources/views/layouts/page.blade.php— base HTML layoutresources/views/sections/— announcement, header, hero, rich-text, content, footerresources/views/blocks/— row, column, text
Options
| Flag | Description |
|---|---|
--force |
Overwrite files that already exist |
--migrate |
Run php artisan migrate immediately after publishing |
# Overwrite existing files and run migrations in one step
php artisan pagebuilder:install --force --migrate
Run migrations (if not using --migrate)
php artisan migrate
Configuration reference
config/pagebuilder.php is published to your application's config/ directory:
return [ // Path to page JSON data files 'pages' => resource_path('views/pages'), // Path to section Blade templates 'sections' => resource_path('views/sections'), // Path to theme block Blade templates 'blocks' => resource_path('views/blocks'), // Path to JSON template files (fallback layouts for pages without a page JSON) 'templates' => resource_path('views/templates'), // Middleware applied to editor routes 'middleware' => ['web'], // Filesystem disk for asset uploads 'disk' => 'public', // Directory within the disk for uploaded assets 'asset_directory' => 'pagebuilder', // Reserved slugs that cannot be used for dynamic pages 'preserved_pages' => ['home'], ];
Publish resources individually
If you need to re-publish a specific resource:
# Config php artisan vendor:publish --tag=pagebuilder-config # Database migrations php artisan vendor:publish --tag=pagebuilder-migrations # Editor frontend assets (React SPA) php artisan vendor:publish --tag=pagebuilder-assets # Built-in package views php artisan vendor:publish --tag=pagebuilder-views
Creating Sections
Sections are the top-level building blocks of a page. Each section is a Blade view that declares its schema using the @schema() directive.
1. Create the Blade file
Place section templates in the configured sections directory (default: resources/views/sections/).
{{-- resources/views/sections/hero.blade.php --}} @schema([ 'name' => 'Hero', 'settings' => [ ['id' => 'title', 'type' => 'text', 'label' => 'Title', 'default' => 'Welcome'], ['id' => 'subtitle', 'type' => 'text', 'label' => 'Subtitle', 'default' => ''], ['id' => 'bg_color', 'type' => 'color', 'label' => 'Background Color', 'default' => '#ffffff'], ], 'blocks' => [ ['type' => 'row'], ['type' => '@theme'], ], 'presets' => [ ['name' => 'Hero'], ['name' => 'Hero with Row', 'blocks' => [ ['type' => 'row', 'settings' => ['columns' => '2']], ]], ], ]) <section {!! $section->editorAttributes() !!} style="background-color: {{ $section->settings->bg_color }}"> <div class="container mx-auto px-4"> <h1>{{ $section->settings->title }}</h1> <p>{{ $section->settings->subtitle }}</p> @blocks($section) </div> </section>
2. Understanding the @schema() array
| Key | Type | Description |
|---|---|---|
name |
string | Required. Human-readable name shown in the editor |
settings |
array | Setting definitions with id, type, label, default |
blocks |
array | Allowed child block types (inline definitions or theme refs) |
presets |
array | Pre-configured templates shown in the "Add section" picker |
max_blocks |
int | Maximum number of child blocks allowed |
3. Section template API
| Property / Method | Description |
|---|---|
$section->id |
Unique instance ID |
$section->type |
Section type identifier (matches filename) |
$section->name |
Human-readable name from schema |
$section->settings->key |
Typed setting access with automatic defaults |
$section->blocks |
BlockCollection of hydrated top-level blocks |
$section->editorAttributes() |
Editor data-* attributes (empty string when not editing) |
@blocks($section) |
Renders all top-level blocks |
Creating Blocks
Blocks are reusable components that live inside sections (or inside other blocks). Block Blade files live in the configured blocks directory (default: resources/views/blocks/).
Theme Blocks
Theme blocks are registered globally and can be referenced by any section that declares ['type' => '@theme'] in its blocks array.
{{-- resources/views/blocks/row.blade.php --}} @schema([ 'name' => 'Row', 'settings' => [ [ 'id' => 'columns', 'type' => 'select', 'label' => 'Columns', 'default' => '2', 'options' => [ [ 'value' => '1', 'label' => '1 Column', ], ['value' => '2', 'label' => '2 Columns'], ['value' => '3', 'label' => '3 Columns'], ], ], [ 'id' => 'gap', 'type' => 'select', 'label' => 'Gap', 'default' => 'md', 'options' => [ [ 'value' => 'none', 'label' => 'None', ], ['value' => 'sm', 'label' => 'Small'], ['value' => 'md', 'label' => 'Medium'], ['value' => 'lg', 'label' => 'Large'], ], ], ], 'blocks' => [ [ 'type' => 'column', 'name' => 'Column', ], ], 'presets' => [ [ 'name' => 'Two Columns', 'settings' => ['columns' => '2'], 'blocks' => [ [ 'type' => 'column', ], ['type' => 'column'], ], ], [ 'name' => 'Three Columns', 'settings' => ['columns' => '3'], 'blocks' => [ [ 'type' => 'column', ], ['type' => 'column'], ['type' => 'column'], ], ], ], ]) <div {!! $block->editorAttributes() !!} class="grid grid-cols-{{ $block->settings->columns }} gap-{{ $block->settings->gap }}"> @blocks($block) </div>
{{-- resources/views/blocks/column.blade.php --}} @schema([ 'name' => 'Column', 'settings' => [ ['id' => 'padding', 'type' => 'select', 'label' => 'Padding', 'default' => 'none', 'options' => [ ['value' => 'none', 'label' => 'None'], ['value' => 'sm', 'label' => 'Small'], ['value' => 'md', 'label' => 'Medium'], ['value' => 'lg', 'label' => 'Large'], ]], ], 'blocks' => [ ['type' => '@theme'], ], ]) <div {!! $block->editorAttributes() !!} class="p-{{ $block->settings->padding }}"> @blocks($block) </div>
Block template API
| Property / Method | Description |
|---|---|
$block->id |
Unique block instance ID |
$block->type |
Block type identifier (matches filename) |
$block->settings->key |
Typed setting access with defaults |
$block->blocks |
BlockCollection of nested child blocks |
$block->editorAttributes() |
Editor data-* attributes |
$section |
Parent section (always available in block views) |
@blocks($block) |
Renders child blocks of this container |
Block Detection: Local vs Theme Reference
In @schema blocks arrays, entries are detected as either local definitions or theme-block references:
| Entry | Type | Detection |
|---|---|---|
['type' => 'column'] |
Theme reference | Only has type key → resolved from block registry |
['type' => '@theme'] |
Wildcard | Accepts any registered theme block |
['type' => 'column', 'name' => 'Column', 'settings' => [...]] |
Local definition | Has extra keys → used as-is |
Page JSON Structure
Pages are stored as JSON files in the configured pages directory. Each page contains sections, their settings, nested blocks, and render order.
{
"title": "Home",
"meta": {
"description": "Welcome to our site"
},
"sections": {
"hero-1": {
"type": "hero",
"settings": {
"title": "Welcome",
"subtitle": "Build amazing pages",
"bg_color": "#f0f0f0"
},
"blocks": {
"row-1": {
"type": "row",
"settings": { "columns": "2", "gap": "md" },
"blocks": {
"col-left": {
"type": "column",
"settings": { "padding": "md" },
"blocks": {}
},
"col-right": {
"type": "column",
"settings": { "padding": "md" },
"blocks": {}
}
},
"order": ["col-left", "col-right"]
}
},
"order": ["row-1"]
}
},
"order": ["hero-1"]
}
Templates
Templates are JSON fallback layouts for pages that have no per-page page builder JSON file and no custom Blade view. They let you define a single file that controls which sections a whole category of pages renders — without requiring a separate pages/{slug}.json for every page.
Page resolution order
1. Custom Blade view pages/{slug}.blade.php (highest priority)
2. Page builder JSON pages/{slug}.json
3. Template JSON templates/{template}.json or templates/page.json
4. 404
Templates are only consulted when both step 1 and step 2 miss. A template never overrides an existing page JSON.
Creating a template
Place template files in resources/views/templates/ (configurable via config('pagebuilder.templates')).
// resources/views/templates/page.json — default template used by all pages { "sections": { "main": { "type": "page-content" } }, "order": ["main"] }
The page.json file is the default template. Any page without a page JSON, and without a specific template selected, renders through it.
Template JSON schema
| Field | Type | Required | Description |
|---|---|---|---|
sections |
object | yes | Section data map — same format as page JSON sections |
order |
string[] | yes | Section render order |
layout |
string | false | no | Layout type (e.g. "page", "full-width"). Defaults to "page". Pass false to render without header/footer zones |
wrapper |
string | no | CSS-selector string that wraps all sections in an HTML element |
Assigning a template to a page
Set the template column on the Page model:
$page = Page::find(1); $page->template = 'page.alternate'; $page->save();
Or when creating a page:
Page::create([ 'title' => 'About Us', 'slug' => 'about', 'template' => 'page.alternate', 'content' => '<p>About our company.</p>', ]);
Template names map to filenames without the .json extension:
template field |
File loaded |
|---|---|
null or "" |
templates/page.json |
"page" |
templates/page.json |
"page.alternate" |
templates/page.alternate.json |
"product" |
templates/product.json |
If the selected template file does not exist, the package falls back to page.json. If page.json also does not exist, a 404 is returned.
The wrapper property
The wrapper field wraps all rendered section HTML in a single HTML element. The value uses a CSS-selector-like syntax:
tag#id.class1.class2[attr1=val1][attr2=val2]
Supported wrapper tags: <div>, <main>, <section>.
{
"wrapper": "div#div_id.div_class[attribute-one=value]",
"sections": { "main": { "type": "page-content" } },
"order": ["main"]
}
Output:
<div id="div_id" class="div_class" attribute-one="value"> <!-- rendered page sections --> </div>
Variable interpolation
Template section settings support {{ $page->attribute }} placeholders. At render time they are replaced with the corresponding attribute from the Page Eloquent model.
{
"sections": {
"hero": {
"type": "hero",
"settings": {
"title": "{{ $page->title }}",
"description": "{{ $page->meta_description }}"
}
},
"main": { "type": "page-content" }
},
"order": ["hero", "main"]
}
Any column on the Page model can be used: title, slug, content, meta_title, meta_description, meta_keywords, or any custom column. Missing or null attributes resolve to an empty string.
Alternative template example
// resources/views/templates/page.alternate.json { "wrapper": "main#page-alternate.page-wrapper", "sections": { "main": { "type": "page-content" } }, "order": ["main"] }
layout: false — rendering without header/footer
Set "layout": false to skip the layout zone system entirely. No @sections('header') or @sections('footer') zones are rendered:
{
"layout": false,
"sections": {
"main": { "type": "hero" }
},
"order": ["main"]
}
Theme-aware templates
If a theme is active, TemplateStorage checks the theme's views/templates/ directory first. This allows themes to override the default page.json template or add new template files without touching the application's templates directory:
themes/my-theme/views/templates/page.json ← overrides app templates/page.json
themes/my-theme/views/templates/product.json ← theme-specific product template
Rendering Pages
In Controllers
use Coderstm\PageBuilder\Facades\Page; class PageController extends Controller { public function show(string $slug) { return Page::render($slug); } }
Programmatic Page Rendering
use Coderstm\PageBuilder\Facades\Page; // Render from slug (loads JSON from disk) $html = Page::render('home'); // Render with extra meta passed to the page model/template $html = Page::render('home', ['title' => 'My Home Page']);
Registering Additional Paths
You can register additional directories for section and block discovery:
use Coderstm\PageBuilder\Facades\Section; use Coderstm\PageBuilder\Facades\Block; // In a service provider's boot() method Section::add(resource_path('views/custom-sections')); Block::add(resource_path('views/custom-blocks'));
Manual Registration
Register a section or block programmatically without a Blade file:
use Coderstm\PageBuilder\Facades\Section; use Coderstm\PageBuilder\Schema\SectionSchema; Section::register('custom-hero', new SectionSchema([ 'name' => 'Custom Hero', 'settings' => [ ['id' => 'title', 'type' => 'text', 'label' => 'Title', 'default' => 'Hello'], ], ]), 'my-views::sections.custom-hero');
Setting Types
The @schema settings array supports these built-in types:
| Type | Description | Extra Keys |
|---|---|---|
text |
Single-line text input | — |
textarea |
Multi-line text input | — |
richtext |
Rich text editor (multi-line) | — |
inline_richtext |
Rich text editor (single-line) | — |
select |
Dropdown select | options: [{value, label}] |
radio |
Radio buttons | options: [{value, label}] |
checkbox |
Boolean toggle | — |
range |
Numeric slider | min, max, step |
number |
Number input | min, max, step |
color |
Color picker (hex) | — |
color_background |
CSS background (gradients) | — |
image_picker |
Media library selector | — |
url |
Link/URL input | — |
video_url |
YouTube/Vimeo URL | — |
icon_fa |
FontAwesome icon picker | — |
icon_md |
Material Design icon picker | — |
text_alignment |
Left/Center/Right segmented ctrl | — |
html |
Raw HTML code editor | — |
blade |
Blade template code editor | — |
header |
Sidebar section divider | content |
paragraph |
Sidebar informational text | content |
external |
Dynamic API-driven selector | — |
Editor
Accessing the Editor
The editor is available at:
GET /pagebuilder/{slug?}
Protect it with authentication middleware in your config:
// config/pagebuilder.php 'middleware' => ['web', 'auth'],
Editor API Endpoints
| Method | URL | Description |
|---|---|---|
GET |
/pagebuilder/pages |
List all pages |
GET |
/pagebuilder/page/{slug} |
Get page JSON |
POST |
/pagebuilder/render-section |
Live-render a section |
POST |
/pagebuilder/save-page |
Save page JSON |
GET |
/pagebuilder/assets |
List uploaded assets |
POST |
/pagebuilder/assets/upload |
Upload an asset |
Editor Helpers
// Check if editor mode is active pb_editor(); // Returns bool // In Blade templates @if(pb_editor()) {{-- Editor-only content --}} @endif
Custom Asset Providers
By default the editor stores uploaded assets through the built-in Laravel provider (storage/app/public/pagebuilder). You can replace it with any storage backend — S3, Cloudflare R2, Cloudinary, DigitalOcean Spaces — by passing a custom provider to PageBuilder.init().
Provider interface
A provider is a plain JavaScript object with two async methods:
const myProvider = { // Return a paginated list of assets async list({ page = 1, search = "" } = {}) { // Must return: { data: Asset[], pagination: { page, per_page, total } } }, // Upload a File object, return the stored asset async upload(file) { // Must return: { id, name, url, thumbnail, size, type } }, };
The url field is what gets stored in page JSON and rendered in Blade — it must be a publicly accessible URL.
Registering the provider
<script src="/vendor/pagebuilder/app.js"></script> <script> PageBuilder.init({ baseUrl: "/pagebuilder", assets: { provider: myProvider, }, }); </script>
AWS S3 / DigitalOcean Spaces / Cloudflare R2
Keep uploads server-side through a thin Laravel proxy controller that writes to S3 using Storage::disk('s3'):
const s3Provider = { async list({ page = 1, search = "" } = {}) { const q = new URLSearchParams({ page, q: search }); const res = await fetch(`/api/pagebuilder/assets?${q}`); if (!res.ok) throw new Error("Failed to fetch assets"); return res.json(); }, async upload(file) { const body = new FormData(); body.append("file", file); const res = await fetch("/api/pagebuilder/assets/upload", { method: "POST", headers: { "X-CSRF-TOKEN": document .querySelector('meta[name="csrf-token"]') ?.getAttribute("content") ?? "", }, body, }); if (!res.ok) throw new Error("Upload failed"); return res.json(); }, };
For Spaces/R2, configure the S3-compatible endpoint in .env — no JS changes required:
AWS_ENDPOINT=https://nyc3.digitaloceanspaces.com # Spaces # or AWS_ENDPOINT=https://<account>.r2.cloudflarestorage.com # R2 AWS_USE_PATH_STYLE_ENDPOINT=true
Cloudinary (direct browser upload)
const cloudinaryProvider = { async list({ page = 1, search = "" } = {}) { const q = new URLSearchParams({ page, q: search }); const res = await fetch(`/api/pagebuilder/cloudinary/assets?${q}`); if (!res.ok) throw new Error("Failed to fetch assets"); return res.json(); }, async upload(file) { // Get a signed upload preset from your Laravel backend const sigRes = await fetch("/api/pagebuilder/cloudinary/sign", { method: "POST", headers: { "Content-Type": "application/json", "X-CSRF-TOKEN": document .querySelector('meta[name="csrf-token"]') ?.getAttribute("content") ?? "", }, body: JSON.stringify({ filename: file.name }), }); const { signature, timestamp, cloudName, apiKey, folder } = await sigRes.json(); const body = new FormData(); body.append("file", file); body.append("api_key", apiKey); body.append("timestamp", timestamp); body.append("signature", signature); body.append("folder", folder); const up = await fetch( `https://api.cloudinary.com/v1_1/${cloudName}/image/upload`, { method: "POST", body }, ); if (!up.ok) throw new Error("Cloudinary upload failed"); const d = await up.json(); return { id: d.public_id, name: d.original_filename, url: d.secure_url, thumbnail: d.secure_url.replace( "/upload/", "/upload/w_200,h_200,c_fill/", ), size: d.bytes, type: `${d.resource_type}/${d.format}`, }; }, };
For the full provider contract and additional examples, see the Developer Documentation.
Blade Directives
| Directive | Description |
|---|---|
@blocks($section) |
Renders all top-level blocks of a section |
@blocks($block) |
Renders child blocks inside a container block |
@schema([...]) |
Declares schema (no-op at render time, extracted at registration) |
@pbEditorClass |
Outputs CSS class when editor mode is active |
Architecture Reference
Key Classes
| Class | Responsibility |
|---|---|
SectionRegistry |
Discovers section Blade files, extracts schemas, provides lookup |
BlockRegistry |
Discovers block Blade files, extracts schemas, provides lookup |
Renderer |
Core rendering engine: hydrates JSON → objects, renders via Blade |
PageRenderer |
Loads page JSON, renders all enabled sections in order |
PageStorage |
Reads/writes page JSON files to disk |
PagePublisher |
Compiles pages into static Blade files |
PageBuilder |
Static API for editor mode, CSS/JS asset URLs |
Reporting Issues
When reporting bugs, please include:
- PHP and Laravel versions
- Package version
- Steps to reproduce
- Expected vs actual behavior
- Relevant error messages or logs
Layout Sections
Pages can define a layout key for per-page overrides of structural slots (header, footer) that live outside the main @yield('content') block in your Blade layout.
{
"sections": { "..." },
"order": ["hero"],
"layout": {
"type": "page",
"header": {
"sections": {
"header": {
"type": "site-header",
"settings": { "sticky": true },
"blocks": {},
"order": [],
"disabled": false
}
}
},
"footer": {
"sections": {
"footer": {
"type": "site-footer",
"settings": {},
"blocks": {},
"order": [],
"disabled": false
}
}
},
}
}
Render layout sections in your Blade layout file using @sections():
{{-- resources/views/layouts/page.blade.php --}} <html class="dark" lang="{{ str_replace('_', '-', app()->getLocale()) }}"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="keywords" content="{{ $meta_keywords ?? '' }}" /> <meta name="description" content="{{ $meta_description ?? '' }}" /> <meta name="author" content="{{ $url ?? config('app.url') }}" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <title> {{ $meta_title ?? ($title ?? '') . ' | ' . config('app.name') }} </title> <!-- Fonts and Icons --> <link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap" rel="stylesheet"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css" /> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@mdi/font@7.4.47/css/materialdesignicons.min.css" /> ... @stack('content_for_head') </head> <body class="page-layout overflow-x-hidden antialiased"> @sections('header') @yield('content') @sections('footer') </body> </html>
Layout sections are non-sortable — their position is determined by the Blade layout. In the editor they appear as fixed rows above and below the sortable page section list.
Rules:
- Keys that match
"header"or carryposition: "top"render in the top zone; everything else goes to the bottom zone. disabled: truecauses@sections()to return an empty string for that slot._nameoverrides the schema display name in the editor (same as page sections).
Theme Integration
The package integrates with qirolab/laravel-themer for multi-theme support.
Register Theme Sections and Blocks
If you're using a theme system you can set the active theme and the package will automatically register theme sections and blocks when the expected view paths exist. This is convenient when using a theme package or qirolab/laravel-themer.
use Coderstm\PageBuilder\Facades\Theme; use Coderstm\PageBuilder\Facades\Section; use Coderstm\PageBuilder\Facades\Block; // Set the active theme (for example in a ThemeServiceProvider or middleware) Theme::set('my-theme'); // The package will automatically register the following directories if they exist: // themes/my-theme/views/sections // themes/my-theme/views/blocks // If you need to register additional paths manually you can still call: Section::add(base_path('themes/my-theme/views/sections')); Block::add(base_path('themes/my-theme/views/blocks'));
Global Theme Settings
Define global design tokens (colors, fonts, spacing) in config/pagebuilder.php. Settings are grouped for display in the editor's Theme Settings panel:
'theme_settings_schema' => [ [ 'name' => 'Colors', 'settings' => [ [ 'key' => 'colors.primary', 'label' => 'Primary', 'type' => 'color', 'default' => '#10b981', 'css_var' => '--colors-primary', ], [ 'key' => 'colors.background_dark', 'label' => 'Background (dark)', 'type' => 'color', 'default' => '#0f0f0f', 'css_var' => '--colors-background-dark', ], ], ], [ 'name' => 'Typography', 'settings' => [ [ 'key' => 'fonts.body', 'label' => 'Body font', 'type' => 'google_font', 'default' => 'Inter, sans-serif', 'css_var' => '--fonts-body', ], ], ], [ 'name' => 'Radius & Shape', 'settings' => [ [ 'key' => 'radius.base', 'label' => 'Radius (base)', 'type' => 'text', 'default' => '0.25rem', 'css_var' => '--radius-base', ], ], ], ],
Schema fields
| Field | Required | Description |
|---|---|---|
key |
Yes | Dot-notation key used to store and retrieve the value (colors.primary) |
type |
Yes | Field type: color, text, select, google_font, etc. |
label |
Yes | Human-readable label shown in the editor panel |
default |
Yes | Fallback value used when no override has been saved |
css_var |
No | CSS custom property (e.g. --colors-primary) updated live in the preview |
css_var — live preview sync
When a css_var is declared on a setting, the editor updates that CSS custom property on the preview iframe's :root in real time as the user types — no page reload required. Declare your tokens in your theme stylesheet to consume them:
:root { --colors-primary: #10b981; --fonts-body: Inter, sans-serif; --radius-base: 0.25rem; } .btn-primary { background-color: var(--colors-primary); } body { font-family: var(--fonts-body); } .card { border-radius: var(--radius-base); }
google_font setting type
Use type: 'google_font' to let editors pick a Google Font from a curated library. The selected font is automatically injected as a <link> tag in the page <head> via the @pbThemeFont Blade directive:
{{-- in your layout <head> --}} @pbThemeFont
Accessing values in Blade
$theme is a ThemeSettings instance shared with all Blade views:
<style> :root { --colors-primary: {{ $theme->get('colors.primary', '#10b981') }}; --fonts-body: {{ $theme->get('fonts.body', 'Inter, sans-serif') }}; } </style>
Use $theme->get('key', 'default') for dot-notation access with a fallback, or $theme->key for top-level keys.
Editor reset options
In the Theme Settings panel editors can:
- Reset individual setting — hover a setting row and click the reset icon to restore its
defaultvalue. - Reset all — click Reset all in the panel header to restore every setting to its schema default in one action. Both reset paths trigger live CSS var updates immediately.
Theme Middleware
You can use the provided ThemeMiddleware to automatically apply themes based on route parameters or session data.
// routes/web.php Route::get('/shop/{theme_slug}', function () { // ... })->middleware('theme:theme_slug');
Artisan Commands
| Command | Description |
|---|---|
pagebuilder:install |
Publish config, migrations, assets, and scaffold starter views |
pagebuilder:install --force |
Same as above, overwriting any existing files |
pagebuilder:install --migrate |
Also run php artisan migrate after publishing |
pages:regenerate |
Rebuild the page registry cache (run after adding/removing page JSON files) |
theme:link |
Symlink theme asset directories into public/themes/ |
theme:link --force |
Overwrite existing symlinks |
License
This package is released under a Non-Commercial Open Source License.
- Free to use, modify, and distribute for non-commercial purposes.
- Commercial use is not permitted without a separate license agreement.
- Contact hello@dipaksarkar.in for commercial licensing.
See LICENSE.md for the full license text.
