Site Configuration
The host-owned declarative settings hierarchy — how a group, section, and item are registered, where values persist, and where each one surfaces in the Shell.
Site Configuration
To read the business timezone in an App, resolve Nexia\Tenancy\Contracts\TenantSettings as shown in App SDK. To add App-owned settings, follow Add a settings page. The registry below is host-only; operators edit its existing surfaces with the required tenant permission.
What it is
Site Configuration is the host-owned path for ordinary scalar tenant settings.
One static registry, App\Settings\SiteConfigRegistry, accepts a three-level
declaration during boot — group → section → item — and everything after
that declaration is generic: rendering, permission emission, and persistence
all derive from the registered metadata.
| Level | Registered with | Carries |
|---|---|---|
| Group | registerConfigGroup() | Dotted key (platform.security), sort, icon, title, area, surface, read/update permission keys |
| Section | registerConfigSection() | Title inside its group |
| Item | registerConfigItem() | Type (text, number, toggle, select, …), default, optional per-item permission |
Values persist in the tenant site_configs table, one row per (key, scope),
read back through SiteConfig::valueFor().
How it fits
Five stages connect a boot() call to a rendered control:
- Register. A service provider declares the group, sections, and items:
SiteConfigRegistry::registerConfigGroup('platform.approval', 170, [
'icon' => 'stamp',
'area' => 'tenant',
'surface' => 'work-management',
'permission' => 'tenant.settings.read',
'update_permission' => 'tenant.settings.update',
]);
SiteConfigRegistry::registerConfigSection('platform.approval', 'line_presets');
SiteConfigRegistry::registerConfigItem(
'platform.approval',
'max_line_presets',
type: 'number',
default: 20,
sectionKey: 'line_presets',
);No label appears in code. Labels live in the locale catalogs under conventional keys derived from the registration keys, resolved per locale at request time:
"site-configuration.groups.approval.label": "Approval Policy",
"site-configuration.groups.line_presets.label": "Approval Line Presets",
"site-configuration.items.max_line_presets.label": "Per-user preset limit"Inline 'title' => __(...) metadata is only a fallback for groups whose
labels are not in the site-configuration.* prefix; when the conventional
key exists, the metadata is ignored.
- Assemble.
TenantSiteConfigurationCatalog::build()walks the registered groups, reads each live value withSiteConfig::valueFor(), and returns thegroups → sections → itemspayload. Identity fields such as the tenant name and symbol are composed in the same catalog directly. - Render.
SettingsConfigRendererdraws that payload generically on the selected settings surface — draft state, controls by itemtype, file preview, and Save. It owns no fetching, no permissions, and no domain rules. - Authorize.
SiteConfigPermissionAdapterwalks every registeredpermissionandupdate_permissionkey and emits it as a permission definition, sonexia-access:sync-permissionslands settings keys in the catalog without per-setting permission classes. - Persist. Saving writes one
site_configsrow per key. The model carries no validation — the registered type and the consuming controller own the write shape.
The catalog includes a surface discriminator, defaulting to site-configuration. The Approval group above explicitly selects work-management, so it appears at /settings/work-management. Registering a group does not create a navigation destination.
Boundaries
Ordinary values only. The generic path fits scalar values gated by
tenant.settings.read/update with no side effects. A setting that needs its
own permission namespace, an audit event, non-generic validation, or
collection-shaped data gets its own catalog and surface —
TenantSecurityPolicyCatalog stores values in the same site_configs table
but owns tenant.security_policy.* permissions, its own route, and an
auth.security_policy.changed event.
Registration is not navigation. The registry feeds the Site Configuration
payload; the Settings Menu structure comes from destination contributions with
shell.settings context, where group_id picks the user-facing area and
subgroup_id the collapsible subgroup.
Worked example
The tenant branding you see under Settings → Site Configuration is this whole
chain in one screen. platform.tenant carries the default locale and default
business timezone as registered items; the identity section composes the
tenant name, company, contacts, and symbol_url — a file item constrained
to square PNG — directly in TenantSiteConfigurationCatalog.
An administrator with tenant.settings.update opens
/settings/site-configuration, uploads a new symbol, and saves. The renderer
posts to the site-configuration API, the value lands as a site_configs row
(platform.tenant.default_locale) or a tenant column (identity fields), and
every surface that resolves the tenant symbol — login page, Shell sidebar —
reads the new value on its next request.
Related
- Add a settings page — why an App models configuration as a Resource instead
- Configuration Ownership — deployment vs platform vs App-owned configuration
- Permissions and roles — where emitted permission keys land