Appearance
Using Raw Filament Components
FilamentCraft's setting types are a curated DSL — a stable, version-agnostic surface that compiles to Filament form components and wires each one into the live-preview bus. But the DSL is not a wall. Any Filament v4/v5 form component can be dropped straight into a section's settings() array, alongside (or instead of) the DSL types, with no registration, adapter, or wrapper.
This is the escape hatch: reach for it whenever you need a control the DSL doesn't ship — a Repeater, KeyValue, Tabs layout, Builder, or your own custom field.
The one rule
Return the component from settings() like any setting. That's it.
php
use Filament\Forms\Components\TextInput;
use FilamentCraft\Sections\AbstractSection;
use FilamentCraft\Sections\Data\SectionData;
use FilamentCraft\Settings\Types\Text;
final class HeroSection extends AbstractSection
{
public static function settings(): array
{
return [
Text::make('heading')->default('Hello'), // FilamentCraft DSL
TextInput::make('subheading')->default('World'), // raw Filament — both work
];
}
public static function renderToHtml(SectionData $data): string
{
return '<p>'.$data->settings->get('heading').' '.$data->settings->get('subheading').'</p>';
}
}Both values are read the same way in the view — $data->settings->get('id'). The compiler indexes raw components into the same field-descriptor map the DSL uses, so defaults, value resolution, and image routing all behave identically.
What you get for free
When you pass a raw component, the SchemaCompiler does the wiring for you:
Live preview is automatic
Every raw leaf Field is opted into live updates so the iframe refreshes as the user edits — you don't call ->live() yourself. The mode is chosen by widget kind:
- Discrete, one-click widgets —
Toggle,Checkbox,Select,Radio,ToggleButtons,ColorPicker,FileUpload— get instant->live(). They commit on the DOMchangeevent, so a single click round-trips immediately. - Typing widgets —
TextInput,Textarea,RichEditor— get->live(onBlur: true), so a half-typed value doesn't morph the DOM out from under the cursor mid-keystroke.
If you set your own live mode, FilamentCraft leaves it alone:
php
TextInput::make('blurb')->live(debounce: 500); // preserved — not overriddenNested layouts are walked
Layout containers (Grid, Fieldset, Section, …) are recursed into, so their leaf fields get live mode and contribute descriptors just like top-level fields:
php
use Filament\Schemas\Components\Grid;
use Filament\Forms\Components\TextInput;
Grid::make(2)->schema([
TextInput::make('accent'),
TextInput::make('background'),
]);Repeater, Builder, and KeyValue are treated as leaf fields — their inner schemas are nested state, not top-level siblings, so they are not flattened into separate descriptors. The whole repeater value is stored under its own id:
php
use Filament\Forms\Components\Repeater;
use Filament\Forms\Components\TextInput;
Repeater::make('logos')->schema([
TextInput::make('name'),
TextInput::make('url')->url(),
]);
// $data->settings->get('logos') === [['name' => …, 'url' => …], …]File uploads route correctly
A raw FileUpload works out of the box. Its disk, directory, visibility, and accepted types are read off the component; anything you omit falls back to your config('filamentcraft.uploads.*') defaults — the same routing the DSL Image type uses.
php
use Filament\Forms\Components\FileUpload;
// Explicit disk/dir:
FileUpload::make('logo')->image()->disk('s3')->directory('logos')->visibility('public');
// Omit them → falls back to filamentcraft.uploads config:
FileUpload::make('logo')->image();The stored upload reads back as an ImageValue — Stringable (its string form is the public URL), with ->url, ->path, and the srcset() / small() / medium() / large() variant helpers — or null when nothing is uploaded:
blade
<img src="{{ $data->settings->get('logo') }}" />
{{-- or, explicitly: $data->settings->get('logo')?->url --}}Grouping: keep using Group
The collapsible headers in the editor's settings panel are FilamentCraft sentinels — use Group (not Filament's Section) to open one. Everything after a Group — DSL setting or raw component — belongs to it until the next Group. Raw components mix freely inside groups:
php
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\FileUpload;
use FilamentCraft\Settings\Types\Group;
use FilamentCraft\Settings\Types\Text;
public static function settings(): array
{
return [
Group::make('content_group')->label('Content'),
Text::make('heading'), // DSL
TextInput::make('subtitle'), // raw — same group
Group::make('media_group')->label('Media')->collapsed(),
FileUpload::make('hero_image')->image(), // raw — second group
];
}When to use which
| Use the DSL when… | Reach for a raw component when… |
|---|---|
| You want the curated, version-stable API | You need a control the DSL doesn't ship (Repeater, KeyValue, Tabs, Builder) |
You want a structured value object — Spacing/Gradient ->css(), curated Font catalog, ColorScheme | You're integrating a custom Filament field of your own |
| You want insulation from Filament v4 ↔ v5 API drift | You already know the exact Filament API you want |
Most raw fields return plain values — with three exceptions
A raw TextInput gives you a plain string, and a Repeater / KeyValue a plain array. But three raw component types are recognised by the compiler and get the same value objects as their DSL twins: a raw FileUpload reads back as an ImageValue, a raw RichEditor as a RichTextValue, and a raw ColorPicker as a ColorValue. Everything else passes through untransformed. The remaining structured value objects — SpacingValue and GradientValue with their ->css() helpers, the curated Bunny Fonts catalog, color-scheme resolution — only come from their Setting types.
Gotchas
- Ids must be unique across the whole section, exactly like DSL fields. The compiler throws a
LogicExceptionon a duplicate id (DSL or raw). - Use
Group, notSection, for collapsible headers — a raw FilamentSectionwon't slot into the editor's grouping model. - Value transforms apply to three raw types.
FileUpload,RichEditor, andColorPickerread back asImageValue,RichTextValue, andColorValue. For every other raw field (TextInput,Repeater,KeyValue, …) what Filament stores is what your view reads.
See also
- Setting Types overview — the full DSL.
- Custom Sections — where
settings()lives. - Layout & Structure — the
Group/Categorysentinels.
