Skip to content

Actor Slot Values ​

Core lets an artifact say "I need to know the period" — a slot, declared on a module, resolved per request (Slots). The SDK adds the layer Core cannot have: a value a person carries. Alice works in Europe/Vienna and thinks in quarters; Bob in America/New_York and months. Neither sets it per screen, and no report has to ask.

Slot or setting? ​

Both are configuration, and mixing them up costs a refactor. The difference is what the value belongs to:

#[Setting]#[Slot] + an actor value
Answershow is this artifact configuredin what context am I working
Declared onany configurable class — a handler, a provider, an artifactthe module class, and nowhere else
Types13, including arrays and Model7 scalars — no arrays, no Model
Precedencethe scope ladder: Platform < Installation < Tenant < Site < Userone value per actor, or the catalog default
Stored insdk_settings (the declaration) and sdk_setting_valuessdk_slots and sdk_slot_assignments
Read as$context->setting('key')$context->slot('name'), or a resolved map handed to you (below)

A rule of thumb from the specs: settings are for preferences, actor slot values are for business context.

Where the actor's value sits ​

The SDK inserts one source into Core's chain — second, right behind the request:

Request  →  Actor  →  Composition  →  Setting (the catalog default)

First answer wins, and a slot that has been filled is never offered to a later source. So a URL parameter still overrides the person's own value — that is the point of the order: the request is the most specific statement of intent, the actor's value is the standing one, and the catalog default is the floor. Because the default always answers, a catalogued slot always resolves.

Two properties of the actor layer worth knowing:

  • One value per actor and slot. There is no history, no validity window and no supersession: a write replaces, a clear deletes.
  • It is inert without an actor. An anonymous request skips the source entirely rather than failing.

Setting a value ​

In the Partners app ​

The SDK ships the surface: Partners → a partner → Parameters. The tab lists every catalogued slot with its type, its note, its default and the partner's own value, and lets an administrator create, edit or clear one. It needs the setSlotOverride / clearSlotOverride abilities, which come with local_admin.

That is the everyday path, and it is deliberately an administrator's path: the SDK has no self-service screen for a person's own context yet (the Launchpad's profile is where it will live).

From your own code ​

php
use LaravelUi5\Sdk\Parameters\Contracts\ActorParameterWriterInterface;

final class SwitchTimezoneHandler implements SdkActionHandlerInterface
{
    public function __construct(private ActorParameterWriterInterface $writer) {}

    public function handle(
        BusinessTransaction $transaction,
        BusinessContextInterface $context,
        SdkContext $sdk,
    ): ActionResponse {
        $this->writer->set($sdk->actor(), 'timezone', 'Europe/Zurich', $sdk);

        return $transaction->commit();
    }
}

set($forActor, $name, $value, $context) and clear($forActor, $name, $context) are the whole write API. $forActor need not be the acting partner — writing on behalf of someone is first-class, and the row records who did it.

The writer authorizes nothing. It validates, it does not gate — so the gate is your action's #[Act] ability, exactly as it is for the two shipped actions.

EditLevel does not govern this

#[Slot(editable: …)] looks like a write permission and is not one. Nothing checks it when an actor value is written; it survives as the grade of the synthetic setting Core generates per slot, and the Partners table renders it as a badge. If a slot must be harder to change than another, put that in the ability of the action that changes it.

What a write refuses:

SituationResult
the slot is not in the live catalogUnknownSlotException
the slot was declared once and has since been removedRetiredSlotException — its message tells you to run ui5:sync
the name is one of tenant, actor, now, atReservedSlotNameException (locale is deliberately allowed)
the value does not fit the slot's typeInvalidActorParameterValueException

clear() checks what fits a removal: a reserved name throws ReservedSlotNameException, a name that was never a slot UnknownSlotException. A retired slot's value may be cleared — that is clean-up of a value nobody reads any more. Clearing a name that simply has no value deletes nothing and succeeds.

changed in 2.0Before 2.0 `clear()` checked nothing: a reserved or unknown name deleted nothing and reported success.

A seeder may write SlotAssignment directly — the implementation-consultant path for a company's starting values (The First Seed). It bypasses the writer's checks, so sync first and get the names right.

Reading a value ​

An artifact with a slot seat — a tile, a chart, a report — declares what it needs and is handed the values, already resolved:

php
class RevenueTile extends AbstractUi5Tile
{
    public function getRequiredSlots(): array
    {
        return [CoreSlots::Period, CoreSlots::DateFrom, CoreSlots::DateTo];
    }
}
Where you areHow the map arrives
a tile or chart providerthe first argument of getTile($slots, $context) / getChart($slots, $context)
a cardas the query string on the card's manifest URL
a report providerinjected into provide(…, array $slots)
an SDK action handler$business->slot('period') / $business->slots(), after the handler declares SlottableInterface; the request layer is the query string, never the JSON body
since 2.0 Everywhere else — an entity set, a resource, a dialog's action, a command — ask the

context for one slot at a time, without declaring anything:

php
$timezone = $context->slot(CoreSlots::Timezone);   // the query, else the actor's value, else the default

slot() is the same chain as above, actor layer included (Core: Reading a slot anywhere). The context holds no snapshot: every call runs the chain, so a broken ?timezone= fails at the read. Within one request the actor layer remembers what it has looked up, so a value written mid-request is read on the next request.

For the raw actor layer there is ActorParameterReaderInterface: get('timezone') returns the actor's own value or null — no request, no composition, no default. Useful for an administration screen that must show what a person actually set. It is not "the slot value".

Every slot value arrives in the one form its type names — a Date as a Y-m-d string, a DateTime as ISO-8601, a Decimal as a numeric string — whichever source answered (Slots). The reader's get() returns the same form, so an actor's value compares with a resolved one without conversion.

Inspecting the catalog ​

bash
php artisan ui5:slot                 # every slot: name, type, default, note, declaring modules
php artisan ui5:slot timezone        # one slot in detail — and every actor override of it

The second form is the SDK's addition: below the catalog entry it prints a table of every partner who has a value for that slot, with the value and who assigned it. The command is read-only; there is no ui5:slot set.

Why there is a sdk_slots table ​

The registry already knows every declaration, so the mirror exists for the three things memory cannot do: give sdk_slot_assignments a foreign key to point at, let a query join the catalog against a partner's values in one statement, and give a form request something to validate a slot name against.

ui5:sync keeps it in step, and the registry always wins — type, default, note and grade are overwritten from the declaration. A slot that disappears from the catalog is soft-deleted, never dropped: the assignments that reference it must keep their target. Declare it again and the row is restored.

So: after adding or changing a #[Slot], run ui5:sync. Before that, every write to it fails with the retired-slot error — and if your host runs the cached registry, rebuild that too.

What ships, and what doesn't ​

Core declares six slots — locale, timezone, period, date, date_from, date_to — and the SDK declares none of its own. The store, the console and the CLI are exercised end to end; the read path is young, and the first artifact in your product to declare getRequiredSlots() may well be the first anywhere.

Not built, and not promised:

  • No per-tenant or per-site values. The actor is the only holder. A tenant default would be another source in the chain.
  • No role-derived values. A value comes from the actor, not from what they are.
  • No choice validation. A slot's type is checked; whether fortnight is a period your reports know is your code's business.
  • No events and no audit trail. A write records who set it and when, and overwrites the previous value.
  • The Settings app does not show slots. Its catalogue excludes them on purpose; the Partners Parameters tab is their surface. The slot.* rows you may notice in sdk_settings are a by-product of sync and are read by nothing — and the settings writer refuses to override them, so a write there cannot go missing without a word.
  • The cache is per request. Values are memoised for the request that reads them, no longer.

See also ​