osoobe / laravel-settings
A description for laravel-settings.
Requires
- php: ^8.1
- illuminate/support: ^8.0|^9.0|^10.0|^11.0|^12.0
Requires (Dev)
- orchestra/testbench: ^6.0
Suggests
- osoobe/laravel-utilities: Required for getVersionHostnames() and appendVersionHostname() on AppMeta.
This package is auto-updated.
Last update: 2026-05-23 14:04:38 UTC
README
A Laravel package for dynamically storing, overriding, and retrieving Laravel system configs and per-model settings at runtime — without touching .env or config files.
Values are persisted to the database, automatically type-cast on read, and can be cached or encrypted. The package ships two independent storage systems:
AppMeta— global, application-wide key/value settings.ModelMeta— per-Eloquent-model key/value settings, attached via a polymorphic relation.
Install
composer require osoobe/laravel-settings
The service provider is auto-discovered. Run the migrations to create the app_metas and model_metas tables:
php artisan migrate
Use Cases
- Override Laravel config at runtime — store a value in
AppMetaand retrieve it withAppMeta::config('mail.from.address'). The DB value takes precedence overconfig(), so you can change settings without a deploy. - Encrypted credentials — store third-party API keys or passwords encrypted in the DB with
AppMeta::setSecret()and retrieve them decrypted withAppMeta::secret(). - Per-user or per-model settings — attach arbitrary key/value pairs to any Eloquent model (e.g. user preferences, per-tenant config) using the
HasMetastrait. - Feature flags — store boolean flags in
AppMetaand read them anywhere in the application. - Versioned or grouped settings — subclass
AppMetawith a customCATEGORYconstant to namespace settings for a specific domain (e.g.PaymentSettings,MailSettings). - Audit / change history — use the
Historytrait on anyHasMetasmodel to record a diff log of field changes automatically.
AppMeta — Global Application Settings
AppMeta stores application-wide key/value pairs in the app_metas table.
Dynamic Laravel Config Override
AppMeta::config() checks the database first and falls back to Laravel's config(). This means you can override any config value at runtime without changing files or redeploying.
use Osoobe\Laravel\Settings\Models\AppMeta; // Read: DB value takes precedence over config files $name = AppMeta::config('app.name', 'My App'); // Write: persists to DB and updates the runtime config() value AppMeta::setConfig('app.name', 'My New App'); // Later in the same request, config('app.name') also returns 'My New App'
Encrypted Secrets
Use secret / setSecret for values that should be stored encrypted (API keys, passwords, tokens). The value is encrypted with Laravel's encrypt() before being written and decrypted transparently on read.
// Store an API key encrypted AppMeta::setSecret('services.stripe.secret', 'sk_live_...'); // Retrieve decrypted; falls back to config('services.stripe.secret') if not in DB $key = AppMeta::secret('services.stripe.secret');
Basic CRUD
// Read a value (returns the typed PHP value, not the raw string) AppMeta::getMeta('site.maintenance', false); // Write (upsert) AppMeta::updateMeta('site.maintenance', true); // Read or seed if missing AppMeta::getOrCreateMeta('site.launch_date', '2024-01-01'); // Check existence AppMeta::keyExists('site.maintenance'); // bool // Read with permanent cache (auto-creates record if missing) AppMeta::getMetaOrCached('site.maintenance', false); // Write and update cache atomically AppMeta::updateOrCreateWithCache('site.maintenance', false); // All settings grouped by category AppMeta::getAppSettingsByCategory();
Category Scoping via Subclassing
Subclass AppMeta and set a CATEGORY constant to namespace a group of settings. All queries on the subclass are automatically scoped to that category.
class MailSettings extends AppMeta { const CATEGORY = 'mail'; public static function getKeyPrefix(): string { return 'mail_meta_'; } } MailSettings::setConfig('mail.from.address', 'hello@example.com'); MailSettings::config('mail.from.address'); // only searches the 'mail' category
ModelMeta — Per-Model Settings
ModelMeta stores key/value pairs scoped to an individual Eloquent model instance in the model_metas table. Uniqueness is composite on (model_id, model_type, meta_key).
Add the HasMetas trait to any Eloquent model:
use Osoobe\Laravel\Settings\Traits\HasMetas; class User extends Model { use HasMetas; }
Usage
$user = User::find(1); // Read a typed value directly $theme = $user->getMetaValue('theme'); // e.g. 'dark' // Read the ModelMeta record $meta = $user->getMeta('theme'); // Write (upsert) $user->updateMeta('theme', 'dark'); // All settings grouped by category $user->getSettingsByCategory(); // Query scope: find users with a specific meta key/value User::hasMetas('role', 'admin')->get(); User::hasMetas('newsletter')->get(); // key exists, any value
EnumConfig — Enum-Driven Config Keys
Apply EnumConfig to a PHP backed enum whose value is a config/meta key. This gives you a type-safe API for all your application settings.
use Osoobe\Laravel\Settings\Traits\EnumConfig; enum AppSettings: string { use EnumConfig; case SiteName = 'app.name'; case MaintenanceMode = 'app.maintenance'; case StripeKey = 'services.stripe.secret'; } // Read (DB overrides config file) AppSettings::SiteName->config('Default Name'); // Write (persists to DB + updates runtime config) AppSettings::SiteName->setConfig('My App'); // Encrypted secret AppSettings::StripeKey->setSecret('sk_live_...'); AppSettings::StripeKey->secret();
History — Automatic Change Tracking
Add the History trait to any model that already uses HasMetas. It hooks into created and updated Eloquent events and appends a diff to a history meta entry. Implement metaTrack() to declare which fields to watch.
use Osoobe\Laravel\Settings\Traits\HasMetas; use Osoobe\Laravel\Settings\Traits\History; class Order extends Model { use HasMetas, History; public function metaTrack(): array { return ['status', 'total']; } } // Retrieve the full history array $order->getMetaHistory();
Each history entry contains the tracked field values at the time of the event, a history_status ("created" or "updated"), the updated_at timestamp, and a Unix timestamp.
Type Handling
Values are always stored as strings in the database. The meta_type column records the PHP type (string, integer, boolean, array, etc.) and is set automatically on every write. Reading a record via the value accessor or any helper method returns the correctly typed PHP value — arrays are JSON-encoded on write and decoded on read.
Never read meta_value directly. Always use the value accessor or the provided helper methods.
Caching
getMetaOrCached() stores values in Cache::forever() and auto-creates the DB record if it doesn't exist. The cache key is {prefix}{meta_key} — app_meta_ for AppMeta and model_meta_ for ModelMeta. Override getKeyPrefix() on a subclass to avoid collisions.
// Reads from cache; writes to cache + DB on first call AppMeta::getMetaOrCached('site.name', 'My App'); // Write and refresh cache AppMeta::updateOrCreateWithCache('site.name', 'New Name');
Examples
1. Maintenance Mode Middleware
Toggle maintenance mode from the database without touching .env or redeploying.
// app/Http/Middleware/CheckMaintenance.php use Osoobe\Laravel\Settings\Models\AppMeta; class CheckMaintenance { public function handle(Request $request, Closure $next) { if (AppMeta::getMetaOrCached('site.maintenance', false)) { abort(503, 'We are currently down for maintenance.'); } return $next($request); } }
// Turn maintenance on/off from anywhere (e.g. an admin panel) AppMeta::updateOrCreateWithCache('site.maintenance', true); AppMeta::updateOrCreateWithCache('site.maintenance', false);
2. Admin Panel: Override Mail Settings at Runtime
Allow an admin to change the application's mail config through a form. The new values take effect immediately in the same process — no redeploy needed.
// app/Http/Controllers/Admin/MailSettingsController.php use Osoobe\Laravel\Settings\Models\AppMeta; class MailSettingsController extends Controller { public function show() { return view('admin.mail', [ 'from_address' => AppMeta::config('mail.from.address', config('mail.from.address')), 'from_name' => AppMeta::config('mail.from.name', config('mail.from.name')), ]); } public function update(Request $request) { $request->validate([ 'from_address' => 'required|email', 'from_name' => 'required|string', ]); AppMeta::setConfig('mail.from.address', $request->from_address); AppMeta::setConfig('mail.from.name', $request->from_name); return back()->with('success', 'Mail settings updated.'); } }
3. Storing an Encrypted Third-Party API Key
Store sensitive credentials encrypted in the database and retrieve them anywhere.
// Store once (e.g. from an admin form) AppMeta::setSecret('services.stripe.secret', $request->stripe_secret_key); AppMeta::setSecret('services.sendgrid.key', $request->sendgrid_api_key); // Retrieve decrypted in a service class class StripePaymentService { public function client(): \Stripe\StripeClient { return new \Stripe\StripeClient(AppMeta::secret('services.stripe.secret')); } }
4. Enum-Backed Settings for a Payment Service
Use EnumConfig to define all config keys in one place with full IDE autocompletion.
// app/Enums/PaymentConfig.php use Osoobe\Laravel\Settings\Traits\EnumConfig; enum PaymentConfig: string { use EnumConfig; case StripeKey = 'services.stripe.secret'; case Currency = 'payment.currency'; case MaxRetries = 'payment.max_retries'; case WebhookEnabled = 'payment.webhook_enabled'; }
// Seed defaults on first boot (e.g. in a seeder or AppServiceProvider) PaymentConfig::Currency->setConfig('USD'); PaymentConfig::MaxRetries->setConfig(3); PaymentConfig::WebhookEnabled->setConfig(true); PaymentConfig::StripeKey->setSecret(env('STRIPE_SECRET')); // Read anywhere — DB overrides config file $currency = PaymentConfig::Currency->config('USD'); // 'USD' $retries = PaymentConfig::MaxRetries->config(3); // 3 (int) $enabled = PaymentConfig::WebhookEnabled->config(false); // true (bool) $key = PaymentConfig::StripeKey->secret(); // decrypted key
5. Per-User Preferences with HasMetas
Store arbitrary per-user settings without adding columns to the users table.
// app/Models/User.php use Osoobe\Laravel\Settings\Traits\HasMetas; class User extends Authenticatable { use HasMetas; }
// app/Http/Controllers/UserPreferencesController.php class UserPreferencesController extends Controller { public function update(Request $request) { $user = $request->user(); $user->updateMeta('theme', $request->input('theme', 'light')); $user->updateMeta('locale', $request->input('locale', 'en')); $user->updateMeta('notifications_enabled', $request->boolean('notifications_enabled')); return back()->with('success', 'Preferences saved.'); } public function show(Request $request) { $user = $request->user(); return response()->json([ 'theme' => $user->getMetaValue('theme'), 'locale' => $user->getMetaValue('locale'), 'notifications_enabled' => $user->getMetaValue('notifications_enabled'), ]); } }
// Query all admin users by a meta value $admins = User::hasMetas('role', 'admin')->get(); // Query users who have opted into a newsletter (key exists, any value) $subscribers = User::hasMetas('newsletter')->get();
6. Audit Trail with History
Track field changes on any model automatically using the History trait.
// app/Models/Order.php use Osoobe\Laravel\Settings\Traits\HasMetas; use Osoobe\Laravel\Settings\Traits\History; class Order extends Model { use HasMetas, History; public function metaTrack(): array { return ['status', 'total', 'payment_method']; } }
// Every save automatically appends a diff to the 'history' meta key. $order = Order::create(['status' => 'pending', 'total' => 99.99]); // history: [['status' => 'pending', 'total' => 99.99, 'history_status' => 'created', ...]] $order->update(['status' => 'paid']); // history: [..., ['status' => 'paid', 'history_status' => 'updated', 'timestamp' => ...]] // Read the full audit log $history = $order->getMetaHistory(); foreach ($history as $entry) { echo "{$entry['history_status']} at {$entry['updated_at']}: status → {$entry['status']}"; }
7. Grouped Settings via AppMeta Subclass
Subclass AppMeta with a CATEGORY constant to keep domain-specific settings isolated.
// app/Models/Settings/SeoSettings.php use Osoobe\Laravel\Settings\Models\AppMeta; class SeoSettings extends AppMeta { const CATEGORY = 'seo'; public static function getKeyPrefix(): string { return 'seo_meta_'; } }
// All reads and writes are automatically scoped to category = 'seo' SeoSettings::setConfig('seo.meta_title', 'My Site — Home'); SeoSettings::setConfig('seo.meta_description', 'Welcome to my site.'); SeoSettings::updateMeta('seo.robots', 'index, follow'); $title = SeoSettings::config('seo.meta_title'); $description = SeoSettings::getMeta('seo.meta_description'); // Retrieve all SEO settings grouped by category $grouped = SeoSettings::getAppSettingsByCategory();
Testing
composer test # or vendor/bin/phpunit
Changelog
Please see CHANGELOG for more information what has changed recently.
Contributing
Please see CONTRIBUTING for details.
Credits
Security
If you discover any security-related issues, please email b4.oshany@gmail.com instead of using the issue tracker.
License
The MIT License (MIT). Please see License File for more information.