Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Settings Laravel Package

pharaonic/laravel-settings

A Laravel package for managing application settings through a simple, flexible API. Persist configuration values in the database, retrieve them easily at runtime, and organize settings cleanly for small apps or larger projects that need centralized control.

View on GitHub
Deep Wiki
Context7

Getting Started

Pin and confirm the API before you build on it. The package had a single tagged release in December 2022. Treat the README as the source of truth, not your memory of a similar package. Before writing code:

  1. composer require pharaonic/laravel-settings in a branch you can throw away.
  2. Publish everything and read it: php artisan vendor:publish --provider="Pharaonic\Laravel\Settings\SettingsServiceProvider" — you want the config/ file and the migration. If the provider class name differs, check composer.json under extra.laravel.providers for the exact FQCN.
  3. Run the migration into a local DB and inspect the resulting table. The schema tells you more than the docs: is the value column text/json, is there a model_type/model_id pair for per-model settings, is there a unique index on key?
  4. Open the source in vendor/pharaonic/laravel-settings/src and read the model and any helper/facade. The public surface is small — a get, a set/put, maybe an all/forget. You need to know the exact method names and whether the helper is a function, a facade, or both.

Where to look, in order: published config/settings.php → SettingsServiceProvider::register()/boot() (what binds, what merges, what publishes, what caches) → the Setting model (casts, table name, mass-assignment) → the migration → the facade class and its @method docblocks. The docblocks in a small package are often the only place the full method list exists.

First use case: one read-only global setting behind a route. Add a site_name row via a seeder or tinker, then read it in a controller:

Route::get('/hello', fn () => view('hello', [
    'name' => setting('site_name', 'My App'),
]));

That single read exercises the whole chain — migration, model, cache, config, fallback — with nothing to unwind if the package turns out to be unusable. Do not start with per-model settings, bulk writes, or an admin UI; those are where the undocumented quirks live.

Implementation Patterns

Wrap it in a settings service, even if it's three methods. You will want to change cache TTLs, add logging on writes, or swap the package later. A thin App\Support\Settings that delegates to the package gives you one file to edit:

final class Settings
{
    public function get(string $key, mixed $default = null): mixed
    {
        return cache()->remember(
            "settings.{$key}",
            now()->addHour(),
            fn () => setting($key, $default),
        );
    }

    public function set(string $key, mixed $value): void
    {
        setting()->set($key, $value); // however the package exposes write
        cache()->forget("settings.{$key}");
    }
}

Wrap reads in Cache::remember regardless of whether the package claims built-in caching. In a 7-star package the cache invalidation on write is the first thing to be wrong. Owning the cache key namespace (settings.) lets you flush precisely and lets you verify by killing the cache in a test.

Use it for runtime-configurable values, not for configuration. Good fits: feature flags, maintenance banner text, contact email, pagination defaults, per-tenant branding. Bad fits: DB credentials, driver selection, anything baked into a service provider at boot. If a value must be present before the first request reaches your app, put it in config/, not in the database.

Per-model settings belong on the model, not scattered through controllers. If the package supports model-scoped settings, expose them through the model (a settings() relation or a small trait) rather than calling the package directly from twelve controllers. A User::setting('timezone', 'UTC') accessor is far easier to mock and to reason about than a package call with $user->id threaded through.

Type discipline at the boundary. Values round-trip through a database column. Cast on read, not on use:

$flags = (array) setting('feature_flags', []);
$limit = (int) setting('page_size', 25);
$enabled = filter_var(setting('beta'), FILTER_VALIDATE_BOOLEAN);

Never branch on a raw string from a settings row — "false", "0", "", and null all walk into truthy branches differently than you expect.

Seed defaults; don't rely on call-site fallbacks everywhere. A SettingsSeeder that inserts the canonical keys with sane values means your admin UI shows real current values, setting('x') never surprises you with a package-internal default, and a fresh DB in CI matches production. Call-site defaults become a second-line defence.

Testing. Settings are persisted state — use RefreshDatabase and seed only the keys under test. If the package caches, call cache()->flush() (or your namespace forget) in setUp(), otherwise test order will bite you. Assert both the happy path and the missing-key path:

it('falls back when unset', function () {
    expect(setting('missing', 'fallback'))->toBe('fallback');
});

Admin surface: build a whitelist, not a generic editor. A loop over Setting::all() lets anyone with access set arbitrary keys, including ones another part of your app trusts. Define the editable keys in config (config('settings.editable')), validate against that list, and log every write with the old and new value.

Gotchas and Tips

  • Last release 2022-12-14 and 7 stars. There is no changelog activity to lean on and no guarantee of a Laravel 11/12 upgrade. Check the composer.json Laravel constraint before you install. If you adopt it, budget for the possibility of forking or wrapping tightly enough that replacing it is a one-file change. This is the single biggest risk and should drive every "should I wrap it" decision toward yes.
  • config:cache interacts badly with runtime reads. If any code path calls config() or env() expecting to see a setting written earlier in the request, it won't after php artisan config:cache. Settings belong in the package's own store or the cache, never written into config(). When behaviour differs between artisan serve and a cached production boot, run php artisan optimize:clear first.
  • Re-publishing config after an upgrade is a no-op without --force. Your published config/settings.php will silently miss new keys added upstream, so config('settings.new_key') returns null and the package falls back to an internal default you never chose. Always diff the vendor config against yours and merge manually.
  • Every read is a query unless you cache. A layout partial that reads three settings renders on every page. Left uncached this is three round-trips per request across the entire app, invisible in a small DB and painful under load. This is why the wrapper above caches on read rather than trusting the package's own caching.
  • Cache invalidation on write is the most common bug. Confirm the package's set actually clears the cache key it uses for get. If it doesn't (or you don't know), own the cache key layer yourself. Add a test: write, read, assert you see the new value in the same process and after cache()->flush().
  • get after set in the same request is where stale caches surface. Read-your-writes is the test that catches a missing invalidation. Assert it explicitly; don't just trust it.
  • Concurrent writes race. A get → mutate → set sequence on a JSON value is not atomic. Two requests can silently lose one another's changes. For anything an admin or user can edit concurrently, wrap in a transaction with a row lock, or store independent scalar keys instead of one blob.
  • Missing keys are silent. setting('typo') returns null and your page shows a blank header, not an exception. In strict environments, wrap the package's get to log when the resolved value is null and the key is in your known-keys list, or assert it in a boot test.
  • Boolean and array round-tripping. Verify how the package stores arrays — a JSON column, a PHP serialize() blob, or a text column with casts on the model. If it's serialize(), that column is not portable across PHP versions and is a real migration risk later. Test with true, false, 0, "", [], and a nested array and check each one round-trips identically. Don't assume; prove.
  • Never store secrets in a settings row. Database dump, a read-only replica, a backup on someone's laptop — an API key stored plaintext in a settings row leaks all of those. Secrets go in env/config() or a encrypted-cast column the package doesn't manage.
  • Per-model reads cause N+1. Rendering a list of 30 users where each view calls $user->setting('theme') is 30 queries (or 30 cache misses). Eager load the settings relation if the package exposes one, or batch-fetch the settings you need with a single whereIn on the model type and keys.
  • Static analysis and facades. Facade @method docblocks in a small package are often incomplete. Expect PHPStan/Larastan false positives on setting() and Setting::. Add a local stub in phpstan.neon rather than lowering your level.
  • Octane. If the package registers a singleton that holds a settings registry, it may bleed between requests under Octane. Check the provider for singleton(); if in doubt, flush the package's cache on RequestTerminated or register a scoped binding in your own provider.
  • Extension points, in order of preference: published config → the package's own cache/store → wrapping reads and writes in your own Settings class → extending the Setting model or the service provider → forking. Reaching for a fork first is the most common regret with a 7-star package — wrap it instead and keep the fork as the last resort.
  • Backup before bulk edits. If you build an admin UI over settings, snapshot the table (or log every write) before enabling it in production. One bad save overwriting the wrong key is a 2am rollback of a database backup otherwise.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
laravel2580/idehelpers
delatbabel/viewpages
anavel/gettext
muhamadrezaar/highcharts
delatbabel/keylists
frankkessler/salesforce-laravel-oauth2-rest
anavel/foundation
wdmtech/enviricons
eii/laravel-captcha
muka/shape-reader
tdt/json
refinery29/php-cs-fixer-config
hotmeteor/eco-env
mvaliolahi/hydrate
wearede/tbcpay-php
parsnick/steak
thibaud-dauce/mattermost-php
multilinguals/apollo-client
antaresbr/collection
kr0lik/phpstan-rules