kamekun/kame-settings
Kame Settings is a Laravel package for managing application settings, letting you store, retrieve, and organize configurable values in your database with a simple, developer-friendly API.
kamekun/kame-settings is a small, single-purpose package: a database-backed key/value store with dot-notation keys, an optional cache layer, and a facade. It was extracted from the KAME CMS (Laravel + Livewire + Tailwind), so it is most comfortable in exactly that kind of admin-panel application.
Install and pin. Because the package is pre-1.0 and has no changelog beyond "Test the new refactor for package.", do not use a caret constraint:
composer require kamekun/kame-settings:0.2.0
Commit the lockfile. Verify the PHP/Laravel constraints in vendor/kamekun/kame-settings/composer.json before installing — the last release is from January 2022 (Laravel 8/9 era), so on Laravel 10+ you should expect to fork or at least adjust constraints.
Publish and migrate. Tag names are not documented, so list them rather than guessing:
php artisan vendor:publish --provider="Kamekun\KameSettings\SettingsServiceProvider"
Then inspect config/kame-settings.php and the published migration, and run:
php artisan migrate
Register the facade if it is not auto-discovered. The package predates some auto-discovery edge cases; if Settings::get() is undefined, add the alias to config/app.php:
'aliases' => [
'Settings' => Kamekun\KameSettings\Facades\Settings::class,
],
Where to look first, in this order:
vendor/kamekun/kame-settings/src/ — the facade's docblock is the real API documentation.config/kame-settings.php — cache store, TTL, and table name.First use case is the canonical one: application-level, runtime-editable values that do not belong in .env — site title, contact email, a support phone number, feature toggles — edited from a Livewire admin screen.
$title = Settings::get('site.title', config('app.name'));
Settings::set('site.title', 'Acme Dashboard');
Never call the facade directly from views or scattered controllers. Wrap it on day one. The package is at 0.2.0 with no changelog, so assume you will eventually replace or fork it; a single wrapping class makes that a one-file change and gives you a place for typed casts and a canonical key list.
namespace App\Support;
final class AppSettings
{
public const SITE_TITLE = 'site.title';
public const SUPPORT_EMAIL = 'site.support_email';
public const BETA_ENABLED = 'features.beta';
public static function title(): string
{
return (string) \Settings::get(self::SITE_TITLE, config('app.name'));
}
public static function betaEnabled(): bool
{
return filter_var(
\Settings::get(self::BETA_ENABLED, false),
FILTER_VALIDATE_BOOLEAN
);
}
public static function set(string $key, mixed $value): void
{
\Settings::set($key, $value);
}
}
Constants beat string literals: a typo in 'site.titel' returns the default silently and you lose an hour.
Cast at the boundary, not in the database. The store almost certainly holds strings. Keep the raw store dumb and do typed coercion in the wrapper. If you need richer values (arrays, booleans, dates), check whether the migration column is text and serialise JSON yourself with a consistent helper.
Prefer config for boot-time, settings for runtime. Anything Laravel needs before the container is fully up (mail transport, queue connection, filesystem disks) should stay in config/ and .env. Settings are for values that an admin changes while the app is running. Mixing the two is the main architectural mistake with this class of package.
Seed defaults idempotently, so a fresh environment behaves the same as production:
// database/seeders/SettingsSeeder.php
foreach ([
AppSettings::SITE_TITLE => config('app.name'),
AppSettings::BETA_ENABLED => '0',
] as $key => $value) {
if (\Settings::get($key) === null) {
\Settings::set($key, $value);
}
}
Run it in your deploy pipeline after migrate. Use "set if missing" semantics, not blind overwrite, or you will clobber production values on every deploy.
Livewire admin form pattern. This is the intended use case:
class SettingsForm extends Component
{
public string $title = '';
public string $supportEmail = '';
public bool $beta = false;
public function mount(): void
{
$this->title = AppSettings::title();
$this->supportEmail = AppSettings::supportEmail();
$this->beta = AppSettings::betaEnabled();
}
public function save(): void
{
$data = $this->validate([
'title' => ['required', 'string', 'max:120'],
'supportEmail' => ['required', 'email'],
'beta' => ['boolean'],
]);
AppSettings::set(AppSettings::SITE_TITLE, $data['title']);
AppSettings::set(AppSettings::SUPPORT_EMAIL, $data['supportEmail']);
AppSettings::set(AppSettings::BETA_ENABLED, $data['beta'] ? '1' : '0');
$this->dispatch('settings-saved');
}
}
Hydrate in mount(), validate on save, write each key explicitly. Avoid a generic "loop over request input and set everything" saver — it makes it trivial for a crafted request to overwrite keys you never exposed.
Bulk reads. If you render a page that reads ten settings, verify whether the facade issues one query per get() or loads the table once. If it is per-key, add a memoisation layer in your wrapper (a static array populated on first access) rather than relying on the package's cache.
Testing. Use RefreshDatabase and seed the settings your test needs in setUp(); do not depend on whatever is in the developer's local settings table. If you keep the wrapper class, you can also swap it for a fake in tests and avoid touching the table at all.
boot() is a classic footgun. During migrate, in queue workers booting on a fresh database, or in CI before the table exists, a settings read will throw. Either guard it:
if (! Schema::hasTable('settings')) { return; }
or — better — do not read settings at boot at all.config/kame-settings.php points at the file driver, each web node has its own cache and an admin change made on node A will not appear on node B until TTL expiry. Use a shared store (Redis/Memcached) or flush explicitly in the save path.php artisan config:cache interaction. After publishing or editing the package config, run config:clear locally. In production, make sure the config cache is rebuilt as part of deploy, or config changes silently do nothing.config/kame-settings.php against the vendor copy.'a.b': as a nested lookup or as a literal column value. If it is a literal key, you can safely use dots in names; if it explodes on ., a key containing a dot will never round-trip. Test this once with set('a.b', 1) then get('a.b').get('x', false) and get('x', '0') behave differently under loose comparison. Normalise with filter_var(..., FILTER_VALIDATE_BOOLEAN) in your wrapper and be consistent about how you store booleans ('1'/'0') so get and set agree..env or an encrypted column. If you must, implement encryption in your wrapper before writing.SettingsServiceProvider registered after the package's. If it does not, fork: it is a small package, and a fork you control is cheaper than a vendored dependency you cannot trust.settings table before any version bump so you can restore values if a refactor changes the storage format.How can I help you explore Laravel packages today?