{"id":47251737,"url":"https://github.com/coders-tm/laravel-page-builder","last_synced_at":"2026-04-19T09:01:19.788Z","repository":{"id":344388115,"uuid":"1181629589","full_name":"coders-tm/laravel-page-builder","owner":"coders-tm","description":"A powerful Laravel page builder using sections, layouts and JSON rendering. Build dynamic pages with a visual editor, reusable sections and multi-layout support.","archived":false,"fork":false,"pushed_at":"2026-04-17T04:38:15.000Z","size":17928,"stargazers_count":44,"open_issues_count":0,"forks_count":3,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-17T06:28:49.659Z","etag":null,"topics":["blade","blade-template","cms","content-management","drag-and-drop","landing-page-builder","laravel","laravel-cms","laravel-package","multi-theme","page-builder","schema-driven","theme-system","visual-editor","website-builder","website-editor"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/coders-tm.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":".github/CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"LICENSE.md","code_of_conduct":".github/CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null},"funding":{"github":"dipaksarkar","patreon":null,"open_collective":null,"ko_fi":null,"tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"lfx_crowdfunding":null,"polar":null,"buy_me_a_coffee":null,"thanks_dev":null,"custom":null}},"created_at":"2026-03-14T12:06:53.000Z","updated_at":"2026-04-17T04:37:11.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/coders-tm/laravel-page-builder","commit_stats":null,"previous_names":["coders-tm/laravel-page-builder"],"tags_count":38,"template":false,"template_full_name":null,"purl":"pkg:github/coders-tm/laravel-page-builder","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/coders-tm%2Flaravel-page-builder","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/coders-tm%2Flaravel-page-builder/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/coders-tm%2Flaravel-page-builder/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/coders-tm%2Flaravel-page-builder/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/coders-tm","download_url":"https://codeload.github.com/coders-tm/laravel-page-builder/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/coders-tm%2Flaravel-page-builder/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32000740,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-18T20:23:30.271Z","status":"online","status_checked_at":"2026-04-19T02:00:07.110Z","response_time":55,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["blade","blade-template","cms","content-management","drag-and-drop","landing-page-builder","laravel","laravel-cms","laravel-package","multi-theme","page-builder","schema-driven","theme-system","visual-editor","website-builder","website-editor"],"created_at":"2026-03-14T15:20:58.914Z","updated_at":"2026-04-19T09:01:19.712Z","avatar_url":"https://github.com/coders-tm.png","language":"TypeScript","funding_links":["https://github.com/sponsors/dipaksarkar"],"categories":[],"sub_categories":[],"readme":"# Laravel Page Builder\n\n\u003cp align=\"center\"\u003e\n\u003ca href=\"https://github.com/coders-tm/laravel-page-builder/actions\"\u003e\u003cimg src=\"https://github.com/coders-tm/laravel-page-builder/workflows/tests/badge.svg\" alt=\"Build Status\"\u003e\u003c/a\u003e\n\u003ca href=\"https://packagist.org/packages/coderstm/laravel-page-builder\"\u003e\u003cimg src=\"https://img.shields.io/packagist/dt/coderstm/laravel-page-builder\" alt=\"Total Downloads\"\u003e\u003c/a\u003e\n\u003ca href=\"https://packagist.org/packages/coderstm/laravel-page-builder\"\u003e\u003cimg src=\"https://img.shields.io/packagist/v/coderstm/laravel-page-builder\" alt=\"Latest Stable Version\"\u003e\u003c/a\u003e\n\u003ca href=\"https://packagist.org/packages/coderstm/laravel-page-builder\"\u003e\u003cimg src=\"https://img.shields.io/packagist/l/coderstm/laravel-page-builder\" alt=\"License\"\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"art/intro.gif\" alt=\"Intro\" /\u003e\n\u003c/p\u003e\n\nA modern page builder for Laravel that allows you to build dynamic pages using layouts, sections and JSON rendering.\nIt includes a visual editor, layout system, reusable sections and multi-theme support.\n\n## Features\n\n- **Blade-native rendering** — sections and blocks are regular Blade views with typed PHP objects\n- **`@schema()` directive** — declare settings, child blocks, and presets directly in Blade templates\n- **Visual editor** — React SPA with iframe live preview, drag-and-drop, and inline text editing\n- **JSON-based storage** — page data stored as JSON files on disk for fast reads and easy version control\n- **JSON templates** — fallback layouts for pages without a per-page JSON; supports `wrapper`, variable interpolation (`{{ $page-\u003etitle }}`), and theme overrides\n- **Per-page Layouts** — site header and footer are configurable per-page, stored in the page JSON\n- **Recursive block nesting** — container blocks (rows, columns) can hold child blocks to any depth\n- **Theme blocks** — register global block types that any section can accept via `@theme` wildcard\n- **21+ Field Types** — from basic text inputs to advanced color pickers, icon selectors, and custom types\n- **Page Meta Persistence** — SEO titles and descriptions are automatically managed and persisted across dynamic and preserved pages\n- **Editor mode** — `data-editor-*` attributes injected only when the editor is active\n- **Publishable assets** — config, views, migrations, and frontend assets can be published independently\n\n## Requirements\n\n- PHP 8.2+\n- Laravel 11.x or 12.x\n\n## Installation\n\n```bash\ncomposer require coderstm/laravel-page-builder\n```\n\nThe package auto-registers its service provider via Laravel's package discovery.\n\n### Run the install command\n\n```bash\nphp artisan pagebuilder:install\n```\n\nThis single command:\n\n1. Publishes `config/pagebuilder.php`\n2. Publishes database migrations\n3. Publishes the compiled editor frontend assets to `public/pagebuilder/`\n4. Scaffolds default starter views into your app:\n   - `resources/views/layouts/page.blade.php` — base HTML layout\n   - `resources/views/sections/` — announcement, header, hero, rich-text, content, footer\n   - `resources/views/blocks/` — row, column, text\n\n**Options**\n\n| Flag        | Description                                            |\n| ----------- | ------------------------------------------------------ |\n| `--force`   | Overwrite files that already exist                     |\n| `--migrate` | Run `php artisan migrate` immediately after publishing |\n\n```bash\n# Overwrite existing files and run migrations in one step\nphp artisan pagebuilder:install --force --migrate\n```\n\n### Run migrations (if not using `--migrate`)\n\n```bash\nphp artisan migrate\n```\n\n### Configuration reference\n\n`config/pagebuilder.php` is published to your application's `config/` directory:\n\n```php\nreturn [\n    // Path to page JSON data files\n    'pages' =\u003e resource_path('views/pages'),\n\n    // Path to section Blade templates\n    'sections' =\u003e resource_path('views/sections'),\n\n    // Path to theme block Blade templates\n    'blocks' =\u003e resource_path('views/blocks'),\n\n    // Path to JSON template files (fallback layouts for pages without a page JSON)\n    'templates' =\u003e resource_path('views/templates'),\n\n    // Middleware applied to editor routes\n    'middleware' =\u003e ['web'],\n\n    // Filesystem disk for asset uploads\n    'disk' =\u003e 'public',\n\n    // Directory within the disk for uploaded assets\n    'asset_directory' =\u003e 'pagebuilder',\n\n    // Reserved slugs that cannot be used for dynamic pages\n    'preserved_pages' =\u003e ['home'],\n];\n```\n\n### Publish resources individually\n\nIf you need to re-publish a specific resource:\n\n```bash\n# Config\nphp artisan vendor:publish --tag=pagebuilder-config\n\n# Database migrations\nphp artisan vendor:publish --tag=pagebuilder-migrations\n\n# Editor frontend assets (React SPA)\nphp artisan vendor:publish --tag=pagebuilder-assets\n\n# Built-in package views\nphp artisan vendor:publish --tag=pagebuilder-views\n```\n\n---\n\n## Creating Sections\n\nSections are the top-level building blocks of a page. Each section is a Blade view that declares its schema using the `@schema()` directive.\n\n### 1. Create the Blade file\n\nPlace section templates in the configured sections directory (default: `resources/views/sections/`).\n\n```blade\n{{-- resources/views/sections/hero.blade.php --}}\n@schema([\n    'name' =\u003e 'Hero',\n    'settings' =\u003e [\n        ['id' =\u003e 'title',    'type' =\u003e 'text',  'label' =\u003e 'Title',    'default' =\u003e 'Welcome'],\n        ['id' =\u003e 'subtitle', 'type' =\u003e 'text',  'label' =\u003e 'Subtitle', 'default' =\u003e ''],\n        ['id' =\u003e 'bg_color', 'type' =\u003e 'color', 'label' =\u003e 'Background Color', 'default' =\u003e '#ffffff'],\n    ],\n    'blocks' =\u003e [\n        ['type' =\u003e 'row'],\n        ['type' =\u003e '@theme'],\n    ],\n    'presets' =\u003e [\n        ['name' =\u003e 'Hero'],\n        ['name' =\u003e 'Hero with Row', 'blocks' =\u003e [\n            ['type' =\u003e 'row', 'settings' =\u003e ['columns' =\u003e '2']],\n        ]],\n    ],\n])\n\n\u003csection {!! $section-\u003eeditorAttributes() !!}\n    style=\"background-color: {{ $section-\u003esettings-\u003ebg_color }}\"\u003e\n    \u003cdiv class=\"container mx-auto px-4\"\u003e\n        \u003ch1\u003e{{ $section-\u003esettings-\u003etitle }}\u003c/h1\u003e\n        \u003cp\u003e{{ $section-\u003esettings-\u003esubtitle }}\u003c/p\u003e\n        @blocks($section)\n    \u003c/div\u003e\n\u003c/section\u003e\n```\n\n### 2. Understanding the `@schema()` array\n\n| Key          | Type   | Description                                                  |\n| ------------ | ------ | ------------------------------------------------------------ |\n| `name`       | string | **Required.** Human-readable name shown in the editor        |\n| `settings`   | array  | Setting definitions with `id`, `type`, `label`, `default`    |\n| `blocks`     | array  | Allowed child block types (inline definitions or theme refs) |\n| `presets`    | array  | Pre-configured templates shown in the \"Add section\" picker   |\n| `max_blocks` | int    | Maximum number of child blocks allowed                       |\n\n### 3. Section template API\n\n| Property / Method              | Description                                                |\n| ------------------------------ | ---------------------------------------------------------- |\n| `$section-\u003eid`                 | Unique instance ID                                         |\n| `$section-\u003etype`               | Section type identifier (matches filename)                 |\n| `$section-\u003ename`               | Human-readable name from schema                            |\n| `$section-\u003esettings-\u003ekey`      | Typed setting access with automatic defaults               |\n| `$section-\u003eblocks`             | `BlockCollection` of hydrated top-level blocks             |\n| `$section-\u003eeditorAttributes()` | Editor `data-*` attributes (empty string when not editing) |\n| `@blocks($section)`            | Renders all top-level blocks                               |\n\n---\n\n## Creating Blocks\n\nBlocks are reusable components that live inside sections (or inside other blocks). Block Blade files live in the configured blocks directory (default: `resources/views/blocks/`).\n\n### Theme Blocks\n\nTheme blocks are registered globally and can be referenced by any section that declares `['type' =\u003e '@theme']` in its `blocks` array.\n\n```blade\n{{-- resources/views/blocks/row.blade.php --}}\n@schema([\n    'name' =\u003e 'Row',\n    'settings' =\u003e [\n        [\n            'id' =\u003e 'columns',\n            'type' =\u003e 'select',\n            'label' =\u003e 'Columns',\n            'default' =\u003e '2',\n            'options' =\u003e [\n                [\n                    'value' =\u003e '1',\n                    'label' =\u003e '1 Column',\n                ],\n                ['value' =\u003e '2', 'label' =\u003e '2 Columns'],\n                ['value' =\u003e '3', 'label' =\u003e '3 Columns'],\n            ],\n        ],\n        [\n            'id' =\u003e 'gap',\n            'type' =\u003e 'select',\n            'label' =\u003e 'Gap',\n            'default' =\u003e 'md',\n            'options' =\u003e [\n                [\n                    'value' =\u003e 'none',\n                    'label' =\u003e 'None',\n                ],\n                ['value' =\u003e 'sm', 'label' =\u003e 'Small'],\n                ['value' =\u003e 'md', 'label' =\u003e 'Medium'],\n                ['value' =\u003e 'lg', 'label' =\u003e 'Large'],\n            ],\n        ],\n    ],\n    'blocks' =\u003e [\n        [\n            'type' =\u003e 'column',\n            'name' =\u003e 'Column',\n        ],\n    ],\n    'presets' =\u003e [\n        [\n            'name' =\u003e 'Two Columns',\n            'settings' =\u003e ['columns' =\u003e '2'],\n            'blocks' =\u003e [\n                [\n                    'type' =\u003e 'column',\n                ],\n                ['type' =\u003e 'column'],\n            ],\n        ],\n        [\n            'name' =\u003e 'Three Columns',\n            'settings' =\u003e ['columns' =\u003e '3'],\n            'blocks' =\u003e [\n                [\n                    'type' =\u003e 'column',\n                ],\n                ['type' =\u003e 'column'],\n                ['type' =\u003e 'column'],\n            ],\n        ],\n    ],\n])\n\n\u003cdiv {!! $block-\u003eeditorAttributes() !!}\n    class=\"grid grid-cols-{{ $block-\u003esettings-\u003ecolumns }} gap-{{ $block-\u003esettings-\u003egap }}\"\u003e\n    @blocks($block)\n\u003c/div\u003e\n```\n\n```blade\n{{-- resources/views/blocks/column.blade.php --}}\n@schema([\n    'name'     =\u003e 'Column',\n    'settings' =\u003e [\n        ['id' =\u003e 'padding', 'type' =\u003e 'select', 'label' =\u003e 'Padding', 'default' =\u003e 'none',\n         'options' =\u003e [\n             ['value' =\u003e 'none', 'label' =\u003e 'None'],\n             ['value' =\u003e 'sm', 'label' =\u003e 'Small'],\n             ['value' =\u003e 'md', 'label' =\u003e 'Medium'],\n             ['value' =\u003e 'lg', 'label' =\u003e 'Large'],\n         ]],\n    ],\n    'blocks' =\u003e [\n        ['type' =\u003e '@theme'],\n    ],\n])\n\n\u003cdiv {!! $block-\u003eeditorAttributes() !!} class=\"p-{{ $block-\u003esettings-\u003epadding }}\"\u003e\n    @blocks($block)\n\u003c/div\u003e\n```\n\n### Block template API\n\n| Property / Method            | Description                                      |\n| ---------------------------- | ------------------------------------------------ |\n| `$block-\u003eid`                 | Unique block instance ID                         |\n| `$block-\u003etype`               | Block type identifier (matches filename)         |\n| `$block-\u003esettings-\u003ekey`      | Typed setting access with defaults               |\n| `$block-\u003eblocks`             | `BlockCollection` of nested child blocks         |\n| `$block-\u003eeditorAttributes()` | Editor `data-*` attributes                       |\n| `$section`                   | Parent section (always available in block views) |\n| `@blocks($block)`            | Renders child blocks of this container           |\n\n### Block Detection: Local vs Theme Reference\n\nIn `@schema` `blocks` arrays, entries are detected as either **local definitions** or **theme-block references**:\n\n| Entry                                                           | Type             | Detection                                          |\n| --------------------------------------------------------------- | ---------------- | -------------------------------------------------- |\n| `['type' =\u003e 'column']`                                          | Theme reference  | Only has `type` key → resolved from block registry |\n| `['type' =\u003e '@theme']`                                          | Wildcard         | Accepts any registered theme block                 |\n| `['type' =\u003e 'column', 'name' =\u003e 'Column', 'settings' =\u003e [...]]` | Local definition | Has extra keys → used as-is                        |\n\n---\n\n## Page JSON Structure\n\nPages are stored as JSON files in the configured pages directory. Each page contains sections, their settings, nested blocks, and render order.\n\n```json\n{\n  \"title\": \"Home\",\n  \"meta\": {\n    \"description\": \"Welcome to our site\"\n  },\n  \"sections\": {\n    \"hero-1\": {\n      \"type\": \"hero\",\n      \"settings\": {\n        \"title\": \"Welcome\",\n        \"subtitle\": \"Build amazing pages\",\n        \"bg_color\": \"#f0f0f0\"\n      },\n      \"blocks\": {\n        \"row-1\": {\n          \"type\": \"row\",\n          \"settings\": { \"columns\": \"2\", \"gap\": \"md\" },\n          \"blocks\": {\n            \"col-left\": {\n              \"type\": \"column\",\n              \"settings\": { \"padding\": \"md\" },\n              \"blocks\": {}\n            },\n            \"col-right\": {\n              \"type\": \"column\",\n              \"settings\": { \"padding\": \"md\" },\n              \"blocks\": {}\n            }\n          },\n          \"order\": [\"col-left\", \"col-right\"]\n        }\n      },\n      \"order\": [\"row-1\"]\n    }\n  },\n  \"order\": [\"hero-1\"]\n}\n```\n\n---\n\n## Templates\n\nTemplates 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.\n\n### Page resolution order\n\n```\n1. Custom Blade view    pages/{slug}.blade.php   (highest priority)\n2. Page builder JSON    pages/{slug}.json\n3. Template JSON        templates/{template}.json or templates/page.json\n4. 404\n```\n\nTemplates are only consulted when both step 1 and step 2 miss. A template never overrides an existing page JSON.\n\n### Creating a template\n\nPlace template files in `resources/views/templates/` (configurable via `config('pagebuilder.templates')`).\n\n```json\n// resources/views/templates/page.json  — default template used by all pages\n{\n    \"sections\": {\n        \"main\": {\n            \"type\": \"page-content\"\n        }\n    },\n    \"order\": [\"main\"]\n}\n```\n\nThe `page.json` file is the **default template**. Any page without a page JSON, and without a specific template selected, renders through it.\n\n### Template JSON schema\n\n| Field | Type | Required | Description |\n|---|---|---|---|\n| `sections` | object | yes | Section data map — same format as page JSON sections |\n| `order` | string[] | yes | Section render order |\n| `layout` | string \\| false | no | Layout type (e.g. `\"page\"`, `\"full-width\"`). Defaults to `\"page\"`. Pass `false` to render without header/footer zones |\n| `wrapper` | string | no | CSS-selector string that wraps all sections in an HTML element |\n\n### Assigning a template to a page\n\nSet the `template` column on the `Page` model:\n\n```php\n$page = Page::find(1);\n$page-\u003etemplate = 'page.alternate';\n$page-\u003esave();\n```\n\nOr when creating a page:\n\n```php\nPage::create([\n    'title'    =\u003e 'About Us',\n    'slug'     =\u003e 'about',\n    'template' =\u003e 'page.alternate',\n    'content'  =\u003e '\u003cp\u003eAbout our company.\u003c/p\u003e',\n]);\n```\n\nTemplate names map to filenames without the `.json` extension:\n\n| `template` field | File loaded |\n|---|---|\n| `null` or `\"\"` | `templates/page.json` |\n| `\"page\"` | `templates/page.json` |\n| `\"page.alternate\"` | `templates/page.alternate.json` |\n| `\"product\"` | `templates/product.json` |\n\nIf 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.\n\n### The `wrapper` property\n\nThe `wrapper` field wraps all rendered section HTML in a single HTML element. The value uses a CSS-selector-like syntax:\n\n```\ntag#id.class1.class2[attr1=val1][attr2=val2]\n```\n\nSupported wrapper tags: `\u003cdiv\u003e`, `\u003cmain\u003e`, `\u003csection\u003e`.\n\n```json\n{\n    \"wrapper\": \"div#div_id.div_class[attribute-one=value]\",\n    \"sections\": { \"main\": { \"type\": \"page-content\" } },\n    \"order\": [\"main\"]\n}\n```\n\nOutput:\n\n```html\n\u003cdiv id=\"div_id\" class=\"div_class\" attribute-one=\"value\"\u003e\n    \u003c!-- rendered page sections --\u003e\n\u003c/div\u003e\n```\n\n### Variable interpolation\n\nTemplate section settings support `{{ $page-\u003eattribute }}` placeholders. At render time they are replaced with the corresponding attribute from the `Page` Eloquent model.\n\n```json\n{\n    \"sections\": {\n        \"hero\": {\n            \"type\": \"hero\",\n            \"settings\": {\n                \"title\":       \"{{ $page-\u003etitle }}\",\n                \"description\": \"{{ $page-\u003emeta_description }}\"\n            }\n        },\n        \"main\": { \"type\": \"page-content\" }\n    },\n    \"order\": [\"hero\", \"main\"]\n}\n```\n\nAny 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.\n\n### Alternative template example\n\n```json\n// resources/views/templates/page.alternate.json\n{\n    \"wrapper\": \"main#page-alternate.page-wrapper\",\n    \"sections\": {\n        \"main\": {\n            \"type\": \"page-content\"\n        }\n    },\n    \"order\": [\"main\"]\n}\n```\n\n### `layout: false` — rendering without header/footer\n\nSet `\"layout\": false` to skip the layout zone system entirely. No `@sections('header')` or `@sections('footer')` zones are rendered:\n\n```json\n{\n    \"layout\": false,\n    \"sections\": {\n        \"main\": { \"type\": \"hero\" }\n    },\n    \"order\": [\"main\"]\n}\n```\n\n### Theme-aware templates\n\nIf 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:\n\n```\nthemes/my-theme/views/templates/page.json         ← overrides app templates/page.json\nthemes/my-theme/views/templates/product.json      ← theme-specific product template\n```\n\n---\n\n## Rendering Pages\n\n### In Controllers\n\n```php\nuse Coderstm\\PageBuilder\\Facades\\Page;\n\nclass PageController extends Controller\n{\n    public function show(string $slug)\n    {\n        return Page::render($slug);\n    }\n}\n```\n\n### Programmatic Page Rendering\n\n```php\nuse Coderstm\\PageBuilder\\Facades\\Page;\n\n// Render from slug (loads JSON from disk)\n$html = Page::render('home');\n\n// Render with extra meta passed to the page model/template\n$html = Page::render('home', ['title' =\u003e 'My Home Page']);\n```\n\n---\n\n## Registering Additional Paths\n\nYou can register additional directories for section and block discovery:\n\n```php\nuse Coderstm\\PageBuilder\\Facades\\Section;\nuse Coderstm\\PageBuilder\\Facades\\Block;\n\n// In a service provider's boot() method\nSection::add(resource_path('views/custom-sections'));\nBlock::add(resource_path('views/custom-blocks'));\n```\n\n### Manual Registration\n\nRegister a section or block programmatically without a Blade file:\n\n```php\nuse Coderstm\\PageBuilder\\Facades\\Section;\nuse Coderstm\\PageBuilder\\Schema\\SectionSchema;\n\nSection::register('custom-hero', new SectionSchema([\n    'name' =\u003e 'Custom Hero',\n    'settings' =\u003e [\n        ['id' =\u003e 'title', 'type' =\u003e 'text', 'label' =\u003e 'Title', 'default' =\u003e 'Hello'],\n    ],\n]), 'my-views::sections.custom-hero');\n```\n\n---\n\n## Setting Types\n\nThe `@schema` settings array supports these built-in types:\n\n| Type               | Description                      | Extra Keys                  |\n| ------------------ | -------------------------------- | --------------------------- |\n| `text`             | Single-line text input           | —                           |\n| `textarea`         | Multi-line text input            | —                           |\n| `richtext`         | Rich text editor (multi-line)    | —                           |\n| `inline_richtext`  | Rich text editor (single-line)   | —                           |\n| `select`           | Dropdown select                  | `options: [{value, label}]` |\n| `radio`            | Radio buttons                    | `options: [{value, label}]` |\n| `checkbox`         | Boolean toggle                   | —                           |\n| `range`            | Numeric slider                   | `min`, `max`, `step`        |\n| `number`           | Number input                     | `min`, `max`, `step`        |\n| `color`            | Color picker (hex)               | —                           |\n| `color_background` | CSS background (gradients)       | —                           |\n| `image_picker`     | Media library selector           | —                           |\n| `url`              | Link/URL input                   | —                           |\n| `video_url`        | YouTube/Vimeo URL                | —                           |\n| `icon_fa`          | FontAwesome icon picker          | —                           |\n| `icon_md`          | Material Design icon picker      | —                           |\n| `text_alignment`   | Left/Center/Right segmented ctrl | —                           |\n| `html`             | Raw HTML code editor             | —                           |\n| `blade`            | Blade template code editor       | —                           |\n| `header`           | Sidebar section divider          | `content`                   |\n| `paragraph`        | Sidebar informational text       | `content`                   |\n| `external`         | Dynamic API-driven selector      | —                           |\n\n---\n\n## Editor\n\n### Accessing the Editor\n\nThe editor is available at:\n\n```\nGET /pagebuilder/{slug?}\n```\n\nProtect it with authentication middleware in your config:\n\n```php\n// config/pagebuilder.php\n'middleware' =\u003e ['web', 'auth'],\n```\n\n### Editor API Endpoints\n\n| Method | URL                           | Description           |\n| ------ | ----------------------------- | --------------------- |\n| `GET`  | `/pagebuilder/pages`          | List all pages        |\n| `GET`  | `/pagebuilder/page/{slug}`    | Get page JSON         |\n| `POST` | `/pagebuilder/render-section` | Live-render a section |\n| `POST` | `/pagebuilder/save-page`      | Save page JSON        |\n| `GET`  | `/pagebuilder/assets`         | List uploaded assets  |\n| `POST` | `/pagebuilder/assets/upload`  | Upload an asset       |\n\n### Editor Helpers\n\n```php\n// Check if editor mode is active\npb_editor(); // Returns bool\n\n// In Blade templates\n@if(pb_editor())\n    {{-- Editor-only content --}}\n@endif\n```\n\n---\n\n## Custom Asset Providers\n\nBy 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()`.\n\n### Provider interface\n\nA provider is a plain JavaScript object with two async methods:\n\n```js\nconst myProvider = {\n  // Return a paginated list of assets\n  async list({ page = 1, search = \"\" } = {}) {\n    // Must return: { data: Asset[], pagination: { page, per_page, total } }\n  },\n\n  // Upload a File object, return the stored asset\n  async upload(file) {\n    // Must return: { id, name, url, thumbnail, size, type }\n  },\n};\n```\n\nThe `url` field is what gets stored in page JSON and rendered in Blade — it must be a publicly accessible URL.\n\n### Registering the provider\n\n```html\n\u003cscript src=\"/vendor/pagebuilder/app.js\"\u003e\u003c/script\u003e\n\u003cscript\u003e\n  PageBuilder.init({\n    baseUrl: \"/pagebuilder\",\n    assets: {\n      provider: myProvider,\n    },\n  });\n\u003c/script\u003e\n```\n\n### AWS S3 / DigitalOcean Spaces / Cloudflare R2\n\nKeep uploads server-side through a thin Laravel proxy controller that writes to S3 using `Storage::disk('s3')`:\n\n```js\nconst s3Provider = {\n  async list({ page = 1, search = \"\" } = {}) {\n    const q = new URLSearchParams({ page, q: search });\n    const res = await fetch(`/api/pagebuilder/assets?${q}`);\n    if (!res.ok) throw new Error(\"Failed to fetch assets\");\n    return res.json();\n  },\n  async upload(file) {\n    const body = new FormData();\n    body.append(\"file\", file);\n    const res = await fetch(\"/api/pagebuilder/assets/upload\", {\n      method: \"POST\",\n      headers: {\n        \"X-CSRF-TOKEN\":\n          document\n            .querySelector('meta[name=\"csrf-token\"]')\n            ?.getAttribute(\"content\") ?? \"\",\n      },\n      body,\n    });\n    if (!res.ok) throw new Error(\"Upload failed\");\n    return res.json();\n  },\n};\n```\n\nFor Spaces/R2, configure the S3-compatible endpoint in `.env` — no JS changes required:\n\n```env\nAWS_ENDPOINT=https://nyc3.digitaloceanspaces.com   # Spaces\n# or\nAWS_ENDPOINT=https://\u003caccount\u003e.r2.cloudflarestorage.com  # R2\nAWS_USE_PATH_STYLE_ENDPOINT=true\n```\n\n### Cloudinary (direct browser upload)\n\n```js\nconst cloudinaryProvider = {\n  async list({ page = 1, search = \"\" } = {}) {\n    const q = new URLSearchParams({ page, q: search });\n    const res = await fetch(`/api/pagebuilder/cloudinary/assets?${q}`);\n    if (!res.ok) throw new Error(\"Failed to fetch assets\");\n    return res.json();\n  },\n  async upload(file) {\n    // Get a signed upload preset from your Laravel backend\n    const sigRes = await fetch(\"/api/pagebuilder/cloudinary/sign\", {\n      method: \"POST\",\n      headers: {\n        \"Content-Type\": \"application/json\",\n        \"X-CSRF-TOKEN\":\n          document\n            .querySelector('meta[name=\"csrf-token\"]')\n            ?.getAttribute(\"content\") ?? \"\",\n      },\n      body: JSON.stringify({ filename: file.name }),\n    });\n    const { signature, timestamp, cloudName, apiKey, folder } =\n      await sigRes.json();\n\n    const body = new FormData();\n    body.append(\"file\", file);\n    body.append(\"api_key\", apiKey);\n    body.append(\"timestamp\", timestamp);\n    body.append(\"signature\", signature);\n    body.append(\"folder\", folder);\n\n    const up = await fetch(\n      `https://api.cloudinary.com/v1_1/${cloudName}/image/upload`,\n      { method: \"POST\", body },\n    );\n    if (!up.ok) throw new Error(\"Cloudinary upload failed\");\n    const d = await up.json();\n\n    return {\n      id: d.public_id,\n      name: d.original_filename,\n      url: d.secure_url,\n      thumbnail: d.secure_url.replace(\n        \"/upload/\",\n        \"/upload/w_200,h_200,c_fill/\",\n      ),\n      size: d.bytes,\n      type: `${d.resource_type}/${d.format}`,\n    };\n  },\n};\n```\n\nFor the full provider contract and additional examples, see the [Developer Documentation](docs/index.md).\n\n---\n\n## Blade Directives\n\n| Directive           | Description                                                       |\n| ------------------- | ----------------------------------------------------------------- |\n| `@blocks($section)` | Renders all top-level blocks of a section                         |\n| `@blocks($block)`   | Renders child blocks inside a container block                     |\n| `@schema([...])`    | Declares schema (no-op at render time, extracted at registration) |\n| `@pbEditorClass`    | Outputs CSS class when editor mode is active                      |\n\n---\n\n## Architecture Reference\n\n### Key Classes\n\n| Class             | Responsibility                                                    |\n| ----------------- | ----------------------------------------------------------------- |\n| `SectionRegistry` | Discovers section Blade files, extracts schemas, provides lookup  |\n| `BlockRegistry`   | Discovers block Blade files, extracts schemas, provides lookup    |\n| `Renderer`        | Core rendering engine: hydrates JSON → objects, renders via Blade |\n| `PageRenderer`    | Loads page JSON, renders all enabled sections in order            |\n| `PageStorage`     | Reads/writes page JSON files to disk                              |\n| `PagePublisher`   | Compiles pages into static Blade files                            |\n| `PageBuilder`     | Static API for editor mode, CSS/JS asset URLs                     |\n\n---\n\n## Reporting Issues\n\nWhen reporting bugs, please include:\n\n- PHP and Laravel versions\n- Package version\n- Steps to reproduce\n- Expected vs actual behavior\n- Relevant error messages or logs\n\n---\n\n## Layout Sections\n\nPages 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.\n\n```json\n{\n    \"sections\": { \"...\" },\n    \"order\": [\"hero\"],\n    \"layout\": {\n        \"type\": \"page\",\n        \"header\": {\n            \"sections\": {\n                \"header\": {\n                    \"type\": \"site-header\",\n                    \"settings\": { \"sticky\": true },\n                    \"blocks\": {},\n                    \"order\": [],\n                    \"disabled\": false\n                }\n            }\n        },\n        \"footer\": {\n            \"sections\": {\n                \"footer\": {\n                    \"type\": \"site-footer\",\n                    \"settings\": {},\n                    \"blocks\": {},\n                    \"order\": [],\n                    \"disabled\": false\n                }\n            }\n        },\n    }\n}\n```\n\nRender layout sections in your Blade layout file using `@sections()`:\n\n```blade\n{{-- resources/views/layouts/page.blade.php --}}\n\u003chtml class=\"dark\" lang=\"{{ str_replace('_', '-', app()-\u003egetLocale()) }}\"\u003e\n\u003chead\u003e\n    \u003cmeta charset=\"utf-8\"\u003e\n    \u003cmeta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\"\u003e\n    \u003cmeta name=\"keywords\" content=\"{{ $meta_keywords ?? '' }}\" /\u003e\n    \u003cmeta name=\"description\" content=\"{{ $meta_description ?? '' }}\" /\u003e\n    \u003cmeta name=\"author\" content=\"{{ $url ?? config('app.url') }}\" /\u003e\n    \u003cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1\" /\u003e\n    \u003ctitle\u003e\n        {{ $meta_title ?? ($title ?? '') . ' | ' . config('app.name') }}\n    \u003c/title\u003e\n\n    \u003c!-- Fonts and Icons --\u003e\n    \u003clink rel=\"preconnect\" href=\"https://fonts.googleapis.com\"\u003e\n    \u003clink rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin\u003e\n    \u003clink href=\"https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700\u0026display=swap\" rel=\"stylesheet\"\u003e\n    \u003clink rel=\"stylesheet\" href=\"https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css\" /\u003e\n    \u003clink rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/@mdi/font@7.4.47/css/materialdesignicons.min.css\" /\u003e\n\n    ...\n\n    @stack('content_for_head')\n\u003c/head\u003e\n\n\u003cbody class=\"page-layout overflow-x-hidden antialiased\"\u003e\n\n    @sections('header')\n\n    @yield('content')\n\n    @sections('footer')\n\u003c/body\u003e\n\n\u003c/html\u003e\n\n```\n\nLayout 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.\n\n**Rules:**\n\n- Keys that match `\"header\"` or carry `position: \"top\"` render in the top zone; everything else goes to the bottom zone.\n- `disabled: true` causes `@sections()` to return an empty string for that slot.\n- `_name` overrides the schema display name in the editor (same as page sections).\n\n---\n\n## Theme Integration\n\nThe package integrates with [qirolab/laravel-themer](https://github.com/qirolab/laravel-themer) for multi-theme support.\n\n### Register Theme Sections and Blocks\n\nIf 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`.\n\n```php\nuse Coderstm\\PageBuilder\\Facades\\Theme;\nuse Coderstm\\PageBuilder\\Facades\\Section;\nuse Coderstm\\PageBuilder\\Facades\\Block;\n\n// Set the active theme (for example in a ThemeServiceProvider or middleware)\nTheme::set('my-theme');\n\n// The package will automatically register the following directories if they exist:\n//   themes/my-theme/views/sections\n//   themes/my-theme/views/blocks\n\n// If you need to register additional paths manually you can still call:\nSection::add(base_path('themes/my-theme/views/sections'));\nBlock::add(base_path('themes/my-theme/views/blocks'));\n```\n\n### Global Theme Settings\n\nDefine global design tokens (colors, fonts, spacing) in `config/pagebuilder.php`. Settings are grouped for display in the editor's Theme Settings panel:\n\n```php\n'theme_settings_schema' =\u003e [\n    [\n        'name' =\u003e 'Colors',\n        'settings' =\u003e [\n            [\n                'key'     =\u003e 'colors.primary',\n                'label'   =\u003e 'Primary',\n                'type'    =\u003e 'color',\n                'default' =\u003e '#10b981',\n                'css_var' =\u003e '--colors-primary',\n            ],\n            [\n                'key'     =\u003e 'colors.background_dark',\n                'label'   =\u003e 'Background (dark)',\n                'type'    =\u003e 'color',\n                'default' =\u003e '#0f0f0f',\n                'css_var' =\u003e '--colors-background-dark',\n            ],\n        ],\n    ],\n    [\n        'name' =\u003e 'Typography',\n        'settings' =\u003e [\n            [\n                'key'     =\u003e 'fonts.body',\n                'label'   =\u003e 'Body font',\n                'type'    =\u003e 'google_font',\n                'default' =\u003e 'Inter, sans-serif',\n                'css_var' =\u003e '--fonts-body',\n            ],\n        ],\n    ],\n    [\n        'name' =\u003e 'Radius \u0026 Shape',\n        'settings' =\u003e [\n            [\n                'key'     =\u003e 'radius.base',\n                'label'   =\u003e 'Radius (base)',\n                'type'    =\u003e 'text',\n                'default' =\u003e '0.25rem',\n                'css_var' =\u003e '--radius-base',\n            ],\n        ],\n    ],\n],\n```\n\n**Schema fields**\n\n| Field     | Required | Description                                                                 |\n| --------- | -------- | --------------------------------------------------------------------------- |\n| `key`     | Yes      | Dot-notation key used to store and retrieve the value (`colors.primary`)    |\n| `type`    | Yes      | Field type: `color`, `text`, `select`, `google_font`, etc.                  |\n| `label`   | Yes      | Human-readable label shown in the editor panel                              |\n| `default` | Yes      | Fallback value used when no override has been saved                         |\n| `css_var` | No       | CSS custom property (e.g. `--colors-primary`) updated live in the preview   |\n\n**`css_var` — live preview sync**\n\nWhen 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:\n\n```css\n:root {\n    --colors-primary: #10b981;\n    --fonts-body: Inter, sans-serif;\n    --radius-base: 0.25rem;\n}\n\n.btn-primary  { background-color: var(--colors-primary); }\nbody          { font-family: var(--fonts-body); }\n.card         { border-radius: var(--radius-base); }\n```\n\n**`google_font` setting type**\n\nUse `type: 'google_font'` to let editors pick a Google Font from a curated library. The selected font is automatically injected as a `\u003clink\u003e` tag in the page `\u003chead\u003e` via the `@pbThemeFont` Blade directive:\n\n```blade\n{{-- in your layout \u003chead\u003e --}}\n@pbThemeFont\n```\n\n**Accessing values in Blade**\n\n`$theme` is a `ThemeSettings` instance shared with all Blade views:\n\n```blade\n\u003cstyle\u003e\n    :root {\n        --colors-primary: {{ $theme-\u003eget('colors.primary', '#10b981') }};\n        --fonts-body: {{ $theme-\u003eget('fonts.body', 'Inter, sans-serif') }};\n    }\n\u003c/style\u003e\n```\n\nUse `$theme-\u003eget('key', 'default')` for dot-notation access with a fallback, or `$theme-\u003ekey` for top-level keys.\n\n**Editor reset options**\n\nIn the Theme Settings panel editors can:\n- **Reset individual setting** — hover a setting row and click the reset icon to restore its `default` value.\n- **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.\n\n### Theme Middleware\n\nYou can use the provided `ThemeMiddleware` to automatically apply themes based on route parameters or session data.\n\n```php\n// routes/web.php\nRoute::get('/shop/{theme_slug}', function () {\n    // ...\n})-\u003emiddleware('theme:theme_slug');\n```\n\n---\n\n## Artisan Commands\n\n| Command                         | Description                                                                 |\n| ------------------------------- | --------------------------------------------------------------------------- |\n| `pagebuilder:install`           | Publish config, migrations, assets, and scaffold starter views              |\n| `pagebuilder:install --force`   | Same as above, overwriting any existing files                               |\n| `pagebuilder:install --migrate` | Also run `php artisan migrate` after publishing                             |\n| `pages:regenerate`              | Rebuild the page registry cache (run after adding/removing page JSON files) |\n| `theme:link`                    | Symlink theme asset directories into `public/themes/`                       |\n| `theme:link --force`            | Overwrite existing symlinks                                                 |\n\n---\n\n## License\n\nThis package is released under a **Non-Commercial Open Source License**.\n\n- Free to use, modify, and distribute for **non-commercial purposes**.\n- **Commercial use is not permitted** without a separate license agreement.\n- Contact [hello@dipaksarkar.in](mailto:hello@dipaksarkar.in) for commercial licensing.\n\nSee [LICENSE.md](LICENSE.md) for the full license text.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcoders-tm%2Flaravel-page-builder","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcoders-tm%2Flaravel-page-builder","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcoders-tm%2Flaravel-page-builder/lists"}