Slots
Introduction
A Slot is a contextual value — a reporting date range, a currency, a locale — that may legitimately arrive from several places. Rather than hard-coding where it comes from, you declare the slot once and let a pipeline resolve it, most specific source first, always ending in a declared default so a slot never resolves to null.
Slots are the third of the three value channels: a #[Parameter] answers which record, a #[Setting] answers how is this configured, and a #[Slot] answers in what context.
The resolution chain
For each requested slot, Core walks these sources and takes the first that provides a value:
- Request — a value supplied on the request (e.g. a query-string override).
- Composition — proposals aggregated from the active dashboard's tree (dashboard → groups → tiles/cards), so a dashboard can set a shared context for everything it contains.
- Setting — the auto-expanded
slot.{name}entry in the settings catalog. - Default — the value you declared on the
#[Slot]itself. Always present; the guaranteed fallback.
The SDK adds an Actor source, directly after Request, for the value a person carries — a user's home currency, their reporting period. One value per person and slot; no validity window. In Core alone, the chain is Request → Composition → Setting → Default. See Actor Slot Values.
In a Core-only installation the Setting step and the Default step yield the same value, because the synthetic setting carries the slot's own default and Core has no storage to override it. The chain earns its keep once a storage layer is present: the SDK inserts its Actor source ahead of Composition, and stored per-person values start flowing without a line of your code changing. It does not replace this step — the catalog default stays the floor.
Declaring a slot
Slots are declared on the module class (mirroring the #[Role] convention), one #[Slot] per value the module owns. On an artifact class the attribute fails at boot rather than being ignored:
use LaravelUi5\Core\Parameters\Attributes\Slot;
use LaravelUi5\Core\Parameters\CoreSlots;
use LaravelUi5\Core\Parameters\Enums\ParameterType;
use LaravelUi5\Core\Parameters\Enums\EditLevel;
enum ReportSlots: string
{
case Region = 'region';
}
#[Slot(ReportSlots::Region,
type: ParameterType::String,
default: 'EU',
editable: EditLevel::User,
note: 'Sales region filter.',
)]
class ReportingModule extends AbstractUi5Module
{
// …
}- Slot identity is an enum case — refactor-safe, and the wire name is the case's
->value('region'). A plain string is accepted; the enum is recommended. defaultis mandatory, may not be null, and must already have the slot's type — every slot resolves to something, and that something is typed. ADate/DateTimedefault may instead be one of the sentinels@today,@nowor@first-of-month, resolved at read time; literal values such as9999-12-31pass through unchanged.noteis mandatory — one short sentence on the slot's purpose (it shows up inui5:slot <name>). Empty notes fail at boot;'TBD'is a permitted placeholder.editable(anEditLevel) declares who may change a stored value — a governance hint. Core defines it but does not enforce it (Core is auth-blind) and carries it into the slot's synthetic setting; whether a storage layer checks it is that layer's decision. The conservative default isEditLevel::Administrator, so user-editability is an explicit opt-in.ParameterType::Modelis not allowed for slots — slots are wire-shaped values, not record references (that's what#[Parameter]is for).
Validation at boot
The registry validates each declaration as it reads it, and every failure is a boot-time LogicException naming the declaring module:
- the name must match
^[a-z][a-zA-Z0-9_]*$; - the note must be non-empty;
- the type must not be
Model; - the default must not be
null; - the default must have the slot's type —
42or'42'for anInteger, not'many'; - a
Date/DateTimesentinel must be one Core knows —@tomorowfails here instead of surviving as a literal string; - the attribute must sit on a module class — on an artifact it is refused rather than silently ignored.
Two modules may declare the same slot — but only if type, default, note and editable level all agree. Any mismatch is a conflict at boot naming both declarers. That is what makes a slot a shared vocabulary rather than a race.
Consuming slots in an artifact
An artifact declares the slots its render depends on via SlottableInterface (getRequiredSlots()); the resolved name → value map is handed to its provider:
class SalesReport extends AbstractUi5Report
{
public function getRequiredSlots(): array
{
return [CoreSlots::DateFrom, CoreSlots::DateTo, ReportSlots::Region];
}
}
// The provider receives the resolved values:
class SalesReportProvider implements DataProviderInterface
{
public function provide(array $slots): array
{
return [
'from' => $slots['date_from'],
'to' => $slots['date_to'],
'region' => $slots['region'],
// …
];
}
}What a provider receives
Each value arrives in the one form its slot's type names, whichever source filled it — a query string, a stored value, a composition proposal or the default:
| Type | Arrives as |
|---|---|
String | string |
Integer | int |
Float | float |
Boolean | bool |
Decimal | numeric string, e.g. '19.99' |
Date | string, Y-m-d, e.g. '2026-09-14' |
DateTime | string, ISO-8601 with offset, e.g. '2026-09-14T08:30:00+02:00' |
Dates are strings on purpose: slots are wire-shaped and travel in URLs and JSON. Parse where you compute — Carbon::parse($slots['date_from']).
A value without such a form is refused, not skipped:
- from the request —
?date_from=somedayanswers 422, naming the slot and the expected form. It does not fall back to the default, which would answer a broken link as if it were fine; - from anywhere else — a stored value or a proposal of the wrong type raises
InvalidSlotSourceValueException, which is logged, because the fault is in code or data.
Every slot an artifact requests must be in the catalog; an unknown name fails before any source is asked.
An artifact can also propose values to the active composition via getSlotProposals() (a name → value map) — e.g. a dashboard tile that sets the currency for the whole dashboard. (See Ui5Dashboard.)
Core's canonical slots
Core ships seven ready-to-use slots — reference them from CoreSlots without re-declaring:
| Slot | Type | Default | Purpose |
|---|---|---|---|
currency | String | EUR | ISO 4217 currency code |
locale | String | en-US | BCP 47 locale tag |
timezone | String | UTC | IANA timezone identifier |
period | String | month | Period grain: day, week, month, quarter, year |
date | Date | @today | Reporting reference date |
date_from | Date | @first-of-month | Reporting window start (inclusive) |
date_to | Date | 9999-12-31 | Reporting window end (inclusive) |
All seven are declared EditLevel::User. They are reserved across every LaravelUi5 installation: a module may re-declare them identically, but may not change them.
The settings bridge
Every slot additionally auto-expands into a synthetic Setting with the key slot.{name}, owned by Core's own namespace (com.laravelui5.core) and flagged synthetic so tooling can label it as auto-expanded. Type maps from ParameterType to ValueType; default, note and editable level carry through.
Two consequences worth knowing:
- a slot is addressable through the settings channel without being declared twice — which is what makes step 3 of the resolution chain possible;
- every settings namespace is a real artifact namespace, with no sentinel pseudo-namespace for slot-derived entries.
The bridge is one-way: a #[Setting] does not become a slot.
Inspecting the catalog
One Artisan command introspects every slot the registry has harvested — omit the argument for the catalog, pass a name to drill into one slot:
php artisan ui5:slot # the whole catalog (name, type, default, note, declarer)
php artisan ui5:slot currency # one slot's catalog entry, and the modules that declare itFrom code, the catalog is on the registry:
$registry->hasSlot('period'); // bool
$registry->getSlot('period'); // SlotCatalogEntry, throws UnknownSlotException
$registry->getAllSlots(); // array<string, SlotCatalogEntry>A SlotCatalogEntry carries name, type, default, note, editable, and declaredBy — the module classes that declared it, in registration order.
Related Links
- Parameters — route-bound resource identifiers
- Settings — declared configuration values
- Ui5Module — where slots are declared
- Ui5Dashboard — composition proposals
- Ui5Registry — where the slot catalog lives