Reading & Writing Settings
A setting is declared in code and changed in the Settings app. Your code reads it and never writes it. This page covers both sides on the server: how a value reaches your code, and the rules that decide who may change it.
Declaring a setting is Core's part. Settings in Core covers #[Setting], its arguments and EditLevel. The SDK adds storage: every declared setting gets a stored default, and administrators override it per scope (Scope Precedence).

Setting or slot
A setting answers how is this configured: a threshold, a limit, a switch. A slot answers in what context: the currency, the locale or the time zone a user works in. Core ships those three as slots. A partner's own slot values are set in the Partners app, not in the Settings app. If a value belongs to the person rather than to the configuration, declare a slot.
Declare it on the artifact
A setting is declared on an artifact class, such as an app or an action — that is the only declaration site there is (Settings). ui5:sync writes each one as the Platform row of that artifact. From then on it appears in the Settings app under the artifact's title.
use LaravelUi5\Core\Parameters\Attributes\Setting;
use LaravelUi5\Core\Parameters\Enums\EditLevel;
use LaravelUi5\Core\Parameters\Enums\ValueType;
#[Setting(
key: 'approval.threshold',
type: ValueType::Integer,
default: 1000,
note: 'Orders above this amount need a second approval.',
level: EditLevel::Administrator,
)]
final class OrdersApp extends OrdersAppBase
{
// …
}The class that reads the value — an action handler, a card provider — declares nothing. It receives its artifact's settings, or reads them through SdkContext, as below.
level defaults to Organization. Lower it if local admins or users should change the value (Who may change a setting).
Reading a setting
In a request, settings reach your code through SdkContext. In an action handler it is the third argument of handle(); elsewhere, inject it.
use LaravelUi5\Sdk\Platform\SdkContext;
$threshold = $sdk->appSetting('approval.threshold'); // a setting of the app
$pageSize = $sdk->setting('pageSize'); // a setting of the artifact the request addresses
$all = $sdk->appSettings(); // every setting of the app, key => valueThe SDK resolves the settings once per request, for the partner who acts. Under impersonation that is the impersonated partner. The value arrives decoded and scope-resolved: the partner's own User value if there is one, otherwise the most specific shared scope, otherwise the default. setting() and appSetting() return null for a key that is not declared.
Which artifact's settings
An app's settings are quasi-global for everything the app contains, so appSetting() reads them wherever you are. The app is the root artifact of the module. setting() reads the artifact the request addresses:
| Request | What setting() returns | What appSetting() returns |
|---|---|---|
| an app, and its OData service | the app's settings | the same |
| an action, a resource, a card | the artifact's own settings | its app's settings |
| a tile on a dashboard | the dashboard's settings | the dashboard's app's settings |
A tile or chart renders inside its dashboard's request and shares its context. A tile that another module contributes to the dashboard therefore reads the dashboard's app, not its own.
On the client
context.json carries every setting of the app, personalized for the acting partner — the partner's own User values included. The shell's settings model and LaravelUi5.getSetting('key') read it. Everything in an app setting therefore reaches the browser of anyone who may open the app: keep secrets out of settings.
Two paths that don't carry stored values
$this->keyon a handler or provider (Core'sAbstractConfigurable) is always the declared default. SDK action handlers don't get it at all: they don't extendAbstractConfigurable, so the read is an undefined property and yieldsnullwith a warning. Use$sdk->appSetting()or$sdk->setting().- Outside a request, in a queued job or a console command, there is no
SdkContext. Read the value in the request and pass it to the job.
The SDK has no public PHP API for reading settings anywhere else, or for writing them.
Changing a setting
Administrators change settings in the Settings app. A change is always an override at one scope. The Platform default belongs to your declaration and changes only with it.
- A
Uservalue belongs to one partner. The other scopes are shared by the whole installation. - Every change records who made it, in
set_by. - Resetting an override deletes that one row. The value falls back to the next scope present.
Who may change a setting
Opening the Settings app needs the settings-admin ability, which comes with the local_admin role. Saving and resetting need setSetting and resetSetting, which the same role carries. Beyond that, every change passes these checks, in this order:
- Not the default. The
Platformrow is never written; onlyui5:syncwrites it. - Declared. A key without a
Platformrow can't be overridden. - Edit level. The partner's edit level must reach the setting's
level. - Scope. A partner writes at their own scope and at more specific ones.
- On behalf. Setting another partner's
Uservalue needsLocalAdminor higher.
The partner's system level (sdk_partners.system_level) decides checks 3 to 5:
| System level | Edit level | Scopes it may write | Another partner's User value |
|---|---|---|---|
User | User | User | no |
LocalAdmin | Administrator | Site, User | yes |
TenantAdmin | Organization | Tenant, Site, User | yes |
OperatorAdmin | Operator | Installation to User | yes |
PlatformOwner | Platform | Installation to User | yes |
The system level grants nothing by itself. Without the local_admin role, a partner can't open the Settings app at any level. So today an administrator sets personal values on a user's behalf. A profile in the Launchpad, where users change their own settings, is planned.
A change that fails a check is refused with its reason, and nothing is stored. So is a value that doesn't fit the setting's type (Type Validation).
One kind of setting is refused before any check: a slot's synthetic setting, slot.* under Core's namespace. The parameter chain never reads an override of it, so one stored here would go unseen. A slot is overridden per person instead, in the Parameters tab of the Partners console (Actor Slot Values).
When a declaration changes
ui5:sync keeps the Platform rows in line with your declarations.
- A changed default, type or level updates the
Platformrow. Overrides keep their values. The next change of an override is checked against the new type. - A removed declaration deletes the
Platformrow and every override of it, at every scope, including personalUservalues. The sync names the count on the terminal, and--drylists each row with its scope and partner before anything is written. - A renamed key is one setting removed and another declared — so the old key's overrides are deleted and the new key starts at its declared default. Carry values across in a migration if you need them.
A rename drops the values
Because a rename is a removal plus a declaration, the overrides do not follow the new key. If people have set values you want to keep, copy them in the same deployment, before ui5:sync runs.
Until 1.2.0 the overrides of a removed declaration were left in the table. They were not conserved by that — they were stranded: a read still answered with the orphan, no console listed it, and the writer refused every set() and reset() because both authorize against a declaration that no longer existed. Deleting them is also what happens one level up, and always has: sdk_settings cascades on artifact_id, so removing a whole module has always taken its overrides with it.
Listening for changes
Two events report changes. Both are in LaravelUi5\Sdk\Settings\Events.
| Event | Fired | Carries |
|---|---|---|
SettingUpdated | after a value was created or changed, not when it stayed the same | $setting, the stored row |
SettingReset | after a reset | $artifactId, $key, $scope, $partnerId |
Both fire inside the action's database transaction, before it commits. A listener that reads the database or queues work should handle the event after the commit (Laravel's ShouldHandleEventsAfterCommit).
See also
- Scope Precedence: which value wins
- Type Validation: what a value must look like
- The Settings App: where administrators change settings
- Settings in Core: declaring a setting