Settings Services ​
Singular settings are stored as JSON sections on IsSingular models and read through a shared API. Three facades expose the same read/write surface with different backing stores and routing rules.
| Facade | Accessor | Service | Model | Sections |
|---|---|---|---|---|
SystemSettings | system.settings | Modules\SystemSetting\Services\SystemSettingsService | General | site, social, contact, seo, smtp, analytics, locale |
CmsSettings | cms.settings | Modules\Cms\Services\CmsSettingsService | SiteSetting | site, social, contact, seo only |
SiteSettings | site.settings | Modules\Cms\Services\SiteSettingsService | (router) | Context-aware — see below |
Abstract base: Unusualify\Modularous\Services\Settings\AbstractSingularSettingsService — shared by SystemSettingsService and CmsSettingsService.
Which facade to use ​
| Context | Prefer | Why |
|---|---|---|
| Public Blade / frontend layouts | SiteSettings | Uses CMS values when filled; falls back to System |
| Admin / panel / console | SystemSettings or SiteSettings::forBackend() | System is the backend source of truth |
| Explicit CMS-only read/write | CmsSettings | No System fallback |
| SMTP, analytics, locale | SystemSettings | Not editable on SiteSetting |
smtp, analytics, and locale exist only on General / SystemSettings. Do not treat them as CMS Site Setting fields.
Context routing (SiteSettings) ​
SiteSettings
├── Frontend (non-panel HTTP) → CmsSettings ──(empty)──► SystemSettings
├── Backend / panel / console → SystemSettings only
└── Forced
├── forFrontend() / forCms() → frontend semantics
└── forBackend() / forSystem() → backend semanticsDetection (when not forced):
- Console → backend (System only)
- HTTP → CMS layer when
Modularous::isPanelUrl()is false; otherwise System only
Direct layer access:
SiteSettings::cms(); // CmsSettingsService
SiteSettings::system(); // SystemSettingsService
SiteSettings::usesCmsLayer(); // boolShared API ​
Available on SystemSettings, CmsSettings, and SiteSettings (and on the two singular services).
| Method | Signature | Description |
|---|---|---|
get | (string $key, mixed $default = null, ?string $locale = null): mixed | Dot-path read with locale / media-leaf expansion |
value | (string $key, mixed $default = null, ?string $locale = null): mixed | Alias of get() |
first | (string $key, mixed $default = null, ?string $locale = null): mixed | Like get(); if the result is a list, returns the first element |
all | (?string $locale = null): array | Locale-resolved tree of all sections |
has | (string $key): bool | Raw key exists in the snapshot (Arr::has) |
filled | (string $key, ?string $locale = null): bool | Resolved value is non-empty after locale/leaf expansion |
set | (string $key, mixed $value): void | Persist via repository, then forget cache |
forgetCache | (): void | Clear request + cache snapshots |
snapshot | (): array | Cached raw section tree (SMTP password decrypted when present) |
Media leaf expansion ​
When a direct path is empty, get / value expand the last segment through locale (and optional list index):
site.logo.frontend→site.logo.{locale}.frontend→site.logo.{locale}.0.frontend→ …site.favicon.original/seo.og_image.originalwork the same way
Locale fallback order: request locale → Modularous fallback locale → *.
SiteSettings fallback rules ​
On the frontend layer:
get/value/first— ifCmsSettings::filled($key)use CMS; otherwise System (then$default)all/snapshot—array_replace_recursive(System, filterEmpty(Cms))so empty CMS leaves do not blank System valueshas/filled— true if either layer has / is filledset— writes to CMS onlyforgetCache— clears both layers
On the backend layer, every method delegates to SystemSettings only.
Usage examples ​
Blade (frontend) ​
{{-- Logo / favicon with media leaf expansion --}}
<img src="{{ SiteSettings::value('site.logo.frontend') }}" alt="{{ SiteSettings::get('site.name', config('app.name')) }}">
<link rel="icon" href="{{ SiteSettings::value('site.favicon.original') ?: asset('favicon.ico') }}">
{{-- SEO defaults --}}
<title>{{ SiteSettings::get('seo.default_meta_title', config('app.name')) }}</title>
<meta name="description" content="{{ SiteSettings::get('seo.default_meta_description', '') }}">
{{-- Social repeater --}}
@foreach (SiteSettings::get('social', []) as $link)
<a href="{{ $link['url'] ?? '#' }}">{{ $link['platform'] ?? '' }}</a>
@endforeachSystem-only (SMTP / backend) ​
use Unusualify\Modularous\Facades\SystemSettings;
$host = SystemSettings::get('smtp.host');
$timezone = SystemSettings::get('locale.timezone', 'UTC');{{-- Admin favicon from System Settings --}}
<link rel="icon" href="{{ SystemSettings::value('site.favicon.original') }}">PHP — facade vs injection ​
use Unusualify\Modularous\Facades\SiteSettings;
use Unusualify\Modularous\Facades\SystemSettings;
use Modules\Cms\Services\SiteSettingsService;
use Modules\SystemSetting\Services\SystemSettingsService;
// Facade
$name = SiteSettings::get('site.name', config('app.name'));
SystemSettings::set('smtp.host', 'smtp.example.com');
// Constructor injection
public function __construct(
protected SiteSettingsService $siteSettings,
protected SystemSettingsService $systemSettings,
) {}
public function logoUrl(): ?string
{
return $this->siteSettings->forFrontend()->value('site.logo.frontend');
}Frontend vs backend routing ​
use Unusualify\Modularous\Facades\SiteSettings;
// Force public-site semantics (CMS → System fallback)
$publicName = SiteSettings::forFrontend()->get('site.name');
// Force admin / System-only semantics
SiteSettings::forBackend()->set('seo.robots_txt', $body);
// Aliases
SiteSettings::forCms(); // === forFrontend()
SiteSettings::forSystem(); // === forBackend()Fallback: empty CMS → System ​
SystemSettings::set('site.name', 'System Name');
SystemSettings::set('site.email', '[email protected]');
// CMS empty → reads System
SiteSettings::forFrontend()->get('site.name'); // "System Name"
SiteSettings::forFrontend()->get('site.email'); // "[email protected]"
CmsSettings::set('site.name', 'Cms Name');
// Filled CMS key wins; still-empty keys keep System
SiteSettings::forFrontend()->get('site.name'); // "Cms Name"
SiteSettings::forFrontend()->get('site.email'); // "[email protected]"
// Backend ignores CMS
SiteSettings::forBackend()->get('site.name'); // "System Name"Analytics keys are System-only; frontend SiteSettings still resolves them via System fallback:
SystemSettings::set('analytics.gtm_id', 'GTM-XXXXXXX');
SiteSettings::forFrontend()->get('analytics.gtm_id'); // "GTM-XXXXXXX"
CmsSettings::filled('analytics.gtm_id'); // falseSet + cache invalidation ​
use Unusualify\Modularous\Facades\CmsSettings;
use Unusualify\Modularous\Facades\SystemSettings;
SystemSettings::set('contact.support_email', '[email protected]');
// set() persists through the repository and calls forgetCache()
CmsSettings::set('site.tagline', 'Welcome');
CmsSettings::forgetCache(); // also safe to call explicitly
// Repository saves (admin forms) also forget system.settings / cms.settings / site.settings cachesCache keys: system_settings.snapshot, cms_settings.snapshot. TTLs:
| Config | Env | Default |
|---|---|---|
modularous.system_settings.cache_ttl | MODULAROUS_SYSTEM_SETTINGS_CACHE_TTL | 3600 |
modularous.cms_settings.cache_ttl | MODULAROUS_CMS_SETTINGS_CACHE_TTL | same as system |
Related support ​
ApplySmtpMailConfig ​
Modules\SystemSetting\Support\ApplySmtpMailConfig — opt-in override of Laravel mail from SystemSettings SMTP.
- Disabled by default; prefer
.envMAIL_* - Enable with
MODULAROUS_SYSTEM_SETTINGS_MAIL_OVERRIDE=true(modularous.system_settings.mail_override_enabled) - Applies only when
smtp.hostis non-empty — setsmail.defaulttosmtpand merges host / port / username / password, plus optionalfromaddress / name - Encryption helper maps
tls/smtp→ Laravel schemesmtp, andssl/smtps→smtps - Booted from
SystemSettingServiceProvider
MODULAROUS_SYSTEM_SETTINGS_MAIL_OVERRIDE=trueAnalyticsScripts ​
Modules\SystemSetting\Support\AnalyticsScripts — builds GTM / optional GA4 snippets.
- Reads via
SiteSettingsService(frontend CMS → System fallback; analytics values live on System) - Enablement: DB
analytics.enabledwhen set; otherwisemodularous.system_settings.analytics_enabled_default/MODULAROUS_ANALYTICS_ENABLED - Validates IDs (
GTM-…,G-…)
{!! app(\Modules\SystemSetting\Support\AnalyticsScripts::class)->headHtml() !!}
{{-- immediately after <body> --}}
{!! app(\Modules\SystemSetting\Support\AnalyticsScripts::class)->bodyOpenHtml() !!}Or via SiteSettings / SystemSettings directly:
SiteSettings::get('analytics.gtm_id');
SystemSettings::get('analytics.enabled');DuplicateSystemSettingsToCmsSettings ​
Modules\Cms\Support\DuplicateSystemSettingsToCmsSettings — one-time seed that copies General sections into an empty SiteSetting (CMS sections only: site, social, contact, seo). Skips when CMS already has content. Invoked from CmsServiceProvider boot (and host operations when needed).
app(\Modules\Cms\Support\DuplicateSystemSettingsToCmsSettings::class)->duplicateIfNeeded();Registration ​
| Binding | Provider |
|---|---|
SystemSettingsService → system.settings | Modules\SystemSetting\Providers\SystemSettingServiceProvider |
CmsSettingsService → cms.settings | Modules\Cms\Providers\CmsServiceProvider |
SiteSettingsService → site.settings | Modules\Cms\Providers\CmsServiceProvider |
Composer aliases: SystemSettings, CmsSettings, SiteSettings → Unusualify\Modularous\Facades\*.