Appearance
A no-code section builder for Laravel and Filament sites
Every page builder hits the same wall: marketing wants a section arrangement nobody shipped, and the only way to get it is a developer, a PHP class and a release. FilamentCraft's no-code section builder for Laravel removes that step, letting people inside the editor compose a new section type from blocks, save it, and place it on any page like a built-in section.
It shipped in v1.26.0 and has been refined through v1.40.0. This post walks through what an editor sees, how the saved section becomes a real section type, and the fences we put around it for sites where the operator is not someone you fully trust.

When a no-code section builder for Laravel makes sense
Use the builder when a section is a composition of copy, images and layout. Use a section class when it needs logic, such as a database query, an API call or a cache strategy.
That split is the whole design. The developer path is still there, documented in custom sections, and nothing about it changed. The builder covers the other case: a client who wants a three-column feature row with a badge above the heading, this week, without a deploy. On a multi-tenant site that request can come from every tenant, and shipping a PHP class per request does not scale.
The builder is on by default. Its definitions live in their own table, so it needs the published migrations:
bash
php artisan vendor:publish --tag=filamentcraft-migrations
php artisan migrateThe knobs sit under one config key:
php
// config/filamentcraft.php
'section_definitions' => [
'enabled' => env('FILAMENTCRAFT_SECTION_DEFINITIONS', true),
'max_per_site' => 50,
'max_nodes' => 100,
'allow_custom_code' => false,
],Starting a section
Editors start from the catalog they already know. Add section opens it with a Create your own card above the built-ins, so there is no separate resource and nobody leaves the editor.
From there, two tabs give two ways in. The Layouts tab has 24 complete arrangements: hero, split content, feature grid, pricing, FAQ, team, timeline, testimonial and others. A layout inserts as ordinary blocks, so nothing is locked and every piece can be moved or deleted afterwards.

The Blocks tab holds 25 primitives in six groups: typography (heading, eyebrow, text, quote, list), actions (button, badge, accordion, callout), media (image, video, person, icon, logo), data and proof (statistic, rating, price, progress, timeline item), layout (container, card, columns, masonry grid, spacer, divider) and an advanced HTML block. Container, card, column and masonry accept children, and trees nest up to four levels deep.
A small v1.26.1 detail: dropping a layout onto a canvas that already has content adds a spacer between the two. Before that, the new rows sat flush against the old ones and read as one run-on section, and every editor added the spacer by hand anyway.
Structure tree and inspector
The Structure tab lists blocks by their own content, not their type. A section with eight text blocks is unreadable as eight rows labelled "Text", so the row shows "Built for work that matters" and the icon carries the type. Rows drag to reorder or reparent, and each has move, duplicate and remove.

Selecting a block swaps the right rail to that block's controls. With nothing selected, the rail shows Section style: color scheme, padding and content width for the whole section. Values come from theme tokens, so a custom section follows the site's color schemes and cannot drift off-brand through a free-form hex field.

Common edits also happen on the canvas. A block carries a small toolbar with drag, add space above, add space below and duplicate. That toolbar went through a few rounds after launch, and the CHANGELOG is honest about them. In v1.39.1 it moved from inside the block to just above it, because buttons, badges and eyebrows are shorter than the toolbar and it covered the very content being edited. In v1.40.0 it stopped following the pointer and started following the selection: passing over a stack of short blocks flickered a different toolbar under every pixel of travel, and it armed actions on blocks the editor had not chosen.
Checking mobile before saving
The device switcher renders the canvas at desktop, tablet and mobile widths. It is the same iframe and the same renderer visitors get, so what reflows in the builder is what reflows on the live site.

The settings panel writes itself
Saving asks for a name and an icon, then Add to page drops the section into the page the editor came from, or Done files it in the catalog. The saved definition appears under Custom sections and can be placed any number of times, each instance with its own content.
The part worth understanding is the settings panel. Every editable prop bound while building (a heading's text, a card's copy, an icon) becomes a setting on the saved section, listed in document order and named after its block. A block with several props prefixes each field, so a button reads Button · Label, Button · Link and Button · Icon. Rich text gets a rich editor, images get an upload, icons get the picker. Since v1.40.0 the image field opens the same site-scoped media library the rest of the editor uses.

The flat list is itself a fix. At launch each block got its own collapsible group, so filling in a three-card feature section meant opening nine groups to reach nine fields. v1.26.1 flattened it. The result is that the person who designs a section and the person who fills it in can be different people, which is the normal arrangement on a client site.
How a definition is stored and published
A definition is a row in filamentcraft_section_definitions with two JSON columns. layout_json is the element tree the renderer walks, and schema_json is the generated settings, in the same shape a coded section's settings serialise to. Both are read by DynamicSectionRenderer, which checks every node's type against an allow-list and resolves every prop through a match with a default, so stored JSON never reaches markup unvalidated.
Custom sections are cacheable. Re-saving one bumps a version counter that folds into the theme-token hash, and that hash is part of the fragment cache key. A re-saved section therefore invalidates its own cached HTML with no manual flush. They also respect Brand Kit limits and render per locale like the built-ins.

The guardrails
The builder is used by people you may not fully trust, so it is fenced on the server, not in the UI:
- Definitions resolve only for their own site. An id from another tenant is refused, not hidden.
- Trees stop at four levels and at
max_nodesblocks. Inserts past either are refused with a notice instead of being dropped at render time. max_per_sitecaps saved definitions. Abandoned drafts are swept and never counted, so a closed browser tab cannot eat an operator's allowance.- The HTML block's markup runs through the shared sanitizer, CSS that could break out of its
<style>is rejected, and<script>output needsallow_custom_code, which is a host developer decision. - Every control value is checked against its declared options or numeric bounds before it reaches the tree.
- Deleting a definition that pages already use tells you how many first, because those instances will render empty.
php artisan filamentcraft:doctor audits stored definitions and reports broken schemas or sites over the limit.
One styling note for hosts that inject their own Tailwind build through filamentcraft.assets.vite_builds. Both bundles emit the same utility names, and before v1.26.0 a host's unlayered .grid-cols-1 could win over our lg:grid-cols-4 on source order and flatten every responsive section to one column. The stylesheet now declares an explicit cascade-layer order, and plain unlayered host CSS still overrides it when you mean it to.
The full walkthrough, including limits, is in the section builder guide. To try it without installing anything, open the live demo and click Add section.
