Skip to content
Concept

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.

LevelRegistered withCarries
GroupregisterConfigGroup()Dotted key (platform.security), sort, icon, title, area, surface, read/update permission keys
SectionregisterConfigSection()Title inside its group
ItemregisterConfigItem()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:

  1. Register. A service provider declares the group, sections, and items:
Code example
PHP
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:

Code example
JSON
"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.

  1. Assemble. TenantSiteConfigurationCatalog::build() walks the registered groups, reads each live value with SiteConfig::valueFor(), and returns the groups → sections → items payload. Identity fields such as the tenant name and symbol are composed in the same catalog directly.
  2. Render. SettingsConfigRenderer draws that payload generically on the selected settings surface — draft state, controls by item type, file preview, and Save. It owns no fetching, no permissions, and no domain rules.
  3. Authorize. SiteConfigPermissionAdapter walks every registered permission and update_permission key and emits it as a permission definition, so nexia-access:sync-permissions lands settings keys in the catalog without per-setting permission classes.
  4. Persist. Saving writes one site_configs row 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.

Source of truth: docs/developers/content/en/operations/site-configuration.md