Fluent settings
Declare a static settings(): Settings method instead of attributes, useful for repeaters, groups, or keeping configuration out of the constructor signature entirely:
use WeArePixel\Inlay\Schema\Settings;
final class Faq extends Component implements InlayBlock
{
public function __construct(
public array $items = [],
) {}
public static function settings(): Settings
{
$itemSchema = Settings::make()
->text('question')->label('Question')->required()
->textarea('answer')->label('Answer');
return Settings::make()->repeater('items', $itemSchema)->label('FAQ items');
}
public function render(): View
{
return view('blocks.faq');
}
}
Settings::make() returns a builder. Each field-defining call (text(), textarea(), richtext(), number(), toggle(), select(), image(), link(), repeater(), group()) starts a new field; the modifier calls that follow it (label(), help(), required(), maxLength(), visibleWhen(), hiddenWhen(), options(), default()) apply to whichever field was most recently defined. Calling a modifier before any field has been defined, or declaring the same field name twice, both throw immediately - see the error table in Registering blocks.
Groups
group() works like repeater() but for a single nested object rather than a list:
public static function settings(): Settings
{
$addressSchema = Settings::make()
->text('street')->label('Street')
->text('city')->label('City');
return Settings::make()->group('address', $addressSchema)->label('Address');
}
Settings carry references, not data
Settings only ever carry the reference to your application's data, never a copy of the data itself - so rendered output always reflects the current state of your own database:
final class FeaturedCollection extends Component implements InlayBlock
{
public function __construct(
public string $heading = 'Featured',
public array $productIds = [],
) {}
public static function settings(): Settings
{
return Settings::make()
->text('heading')->label('Heading')
->repeater('productIds', Settings::make()->text('id')->label('Product ID'))
->label('Products');
}
public function render(): View
{
return view('blocks.featured-collection', [
'products' => Product::query()->whereIn('id', array_column($this->productIds, 'id'))->get(),
]);
}
}
Mixing attributes and fluent definitions
A single block can declare some settings via attributes and others via settings(). When both declare a field with the same name, the fluent declaration wins - the merge adopts every fluent field first, then adopts any attribute-declared field whose name wasn't already claimed.
In practice: use attributes for the ordinary case, and drop into settings() only for the field that needs a repeater, a hand-tuned option list, or anything else attributes can't express. The two never actually collide unless you specifically override one.
Constructor defaults
The same rule from Attribute settings applies here: for a top-level setting, the constructor parameter's own default and nullability are the only source of truth. There's no schema-level .default() at the top level.
.default() on the fluent builder is meaningful only for a field nested inside a repeater or group item schema, which has no top-level constructor parameter to be authoritative instead:
$itemSchema = Settings::make()
->text('question')->label('Question')
->toggle('featured')->label('Featured')->default(false); // fine - nested, no constructor param