idavoll/setting-module
A Laravel settings module for centralized, database-backed configuration management. Store and retrieve typed settings, organize values by group, and integrate modular packages through a simple, consistent API.
This module is a dependency of the AsgardCMS ecosystem, not a general-purpose Laravel settings package. Treat it as part of AsgardCMS first, standalone package second.
Modules loader, and its translation infrastructure. It has effectively zero value outside that stack.asgardcms/platform; install explicitly only if you are pinning versions:
composer require idavoll/setting-module
php artisan module:migrate Setting
Modules/Setting/Providers/SettingServiceProvider.php — bindings, route/menu registration, config merge.Modules/Setting/Contracts/Setting.php — the contract you will inject.Modules/Setting/Repositories/SettingRepository.php — the Eloquent repo, the actual data layer.Modules/Setting/Config/settings.php (if present) — any defaults/group definitions.backend/setting/settings (or /backend#setting) to confirm the admin form renders; this proves migrations, ACL seed, and menu registration all worked.// app/Http/Controllers/HomeController.php
use Modules\Setting\Contracts\Setting;
public function __construct(private Setting $settings) {}
public function index()
{
return view('home', [
'siteName' => $this->settings->get('core::site-name'),
]);
}
If that returns the value you entered in the admin panel, the module is properly wired and you can build on it.1. Inject the contract, not the repository. The contract (Modules\Setting\Contracts\Setting) is the stable interface; the Eloquent repo is an implementation detail that may change between AsgardCMS majors. Constructor-inject for testability:
class MyService
{
public function __construct(private \Modules\Setting\Contracts\Setting $settings) {}
}
2. The Setting facade for view helpers and one-liners. The facade is registered by the service provider. Use it in Blade and quick controller snippets:
<title>{{ Setting::get('core::site-name') }}</title>
3. Namespace your keys with the owning module. All built-in settings use the vendor::key form (core::site-name, core::template, core::locales). Follow this for your own modules — blog::per-page, shop::currency — so that:
page-size key.4. Seed defaults from your module's ServiceProvider boot.
public function boot()
{
if (! $this->app['setting.repository']->has('shop::currency')) {
$this->app['setting.repository']->create([
'name' => 'shop::currency',
'plainValue' => 'EUR',
// value must be present too if the setting is translatable
]);
}
}
Guard with has() so repeated boots don't overwrite admin edits.
5. Translatable settings. Set isTranslatable on the row (usually done through the admin UI or a seeder) and the module will store per-locale values as JSON and return the locale-appropriate string from get(). get('core::site-name') will transparently return the current-locale value, falling back to the default locale. Use plainValue (raw column) when you need something locale-independent, e.g. an API key.
6. Writing at runtime. set() updates the row and refreshes the cache:
Setting::set('shop::currency', 'USD');
Prefer routing all writes through the contract so cache invalidation and translation columns are handled consistently.
7. Wrapping settings in a config service. For anything nontrivial, expose a small typed wrapper rather than sprinkling Setting::get('shop::foo') everywhere — it gives you default handling and a single place to change cache strategy:
class ShopSettings
{
public function __construct(private Setting $settings) {}
public function currency(): string { return $this->settings->get('shop::currency', 'EUR'); }
public function perPage(): int { return (int) $this->settings->get('shop::per-page', 15); }
}
8. Admin integration. Register your setting group in the backend menu under the Setting module if you want a dedicated tab; the module's admin route prefix (backend/setting) can host additional screens. Reuse the existing update route/controller pattern rather than inventing new persistence — the module is the single source of truth for the settings table.
Setting contract's method set and the exact repository binding name (setting.repository) have shifted between AsgardCMS 2.x, 3.x, and 4.x. Pin your AsgardCMS version in composer.json and read the module's own composer.json require block before upgrading. This package is unlikely to publish a migration guide for breaking changes.plainValue vs value. get() returns the translated value; the raw column lives on plainValue. Mixing them up is the single most common bug — non-translatable settings store the same content in both, but translatable ones do not, and reading .value directly yields JSON.Setting::set() invalidate it, but if you bypass the contract and hit the Eloquent model (Setting model) directly — or edit rows in the DB — you must flush the cache (call the repository's clearCache()/flush() or php artisan cache:clear). The DB-backed value and the cached value will otherwise disagree until the TTL expires.null. Always pass a second default argument (get('core::site-name', config('app.name'))) rather than assuming the row exists — especially in fresh installs where your module's seeder hasn't run yet.page-size will stomp each other in the shared settings table. Always use module::key.php artisan module:seed Setting — not a route issue.module:migrate Setting no-ops if the module isn't registered in modules_statuses.json. If you see "table settings not found," check the module's enabled state before debugging the schema.get() returns the default locale for every locale, confirm the translations migration ran and the setting's isTranslatable flag is actually 1.Modules\Setting\Contracts\Setting in a service provider to decorate the module's implementation (e.g. add a Redis layer, or add typed getters). Do this only if you need to; the default caching is usually sufficient.SELECT name, plainValue, value, isTranslatable FROM settings WHERE name = '...' directly. If the row is correct, it's a cache problem. If it's wrong, you're writing through something other than the contract.composer.json ("repositories" with a VCS entry) if you depend on it in production, and read the module source before assuming the facade, contract, or repository binding names above match your exact version.How can I help you explore Laravel packages today?