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

efriandika/laravel-settings

A simple Laravel package for managing database-backed application settings, with helpers to get, set, and forget key-value values and optional caching for fast access.

View on GitHub
Deep Wiki
Context7

Getting Started

What this package is, in one line: a settings table plus a thin facade that gives you get/set for values you want operations or admins to change without a deploy. It is not a replacement for config() and should never hold secrets.

Minimum steps (Laravel 5.x, which is what this package targets):

composer require efriandika/laravel-settings:^1.2

Register the service provider and facade alias manually in config/app.php — the package predates Laravel 5.5 auto-discovery, so php artisan package:discover will not do it for you:

// config/app.php
'providers' => [
    // ...
    Efriandika\LaravelSettings\LaravelSettingsServiceProvider::class,
],
'aliases' => [
    // ...
    'Settings' => Efriandika\LaravelSettings\Facades\Settings::class,
],

Publish and run the migration, then verify the facade surface:

php artisan vendor:publish --provider="Efriandika\LaravelSettings\LaravelSettingsServiceProvider"
php artisan migrate
php artisan tinker
>>> Settings::set('site.tagline', 'Shipping faster');
>>> Settings::get('site.tagline');
>>> Settings::all();

Where to look first, in this order:

  1. vendor/efriandika/laravel-settings/src/ — read the facade and the repository class. It is a small package; you can finish it in ten minutes and you will then know every method signature, whether get() returns null or a default on a miss, and whether writes bust the cache. Do this before writing any application code.
  2. The published config file — this is where cache behaviour and any table/model overrides live.
  3. The migration stub — column names, types, and whether the key is indexed/unique.
  4. composer.json of the package — the illuminate/* and PHP constraints tell you immediately whether this can ever run on your stack.

First real use case: a "branding and contact" settings screen — site name, support email, footer text, a few feature toggles — that marketing can edit from an admin form. That is exactly the shape this package is built for, and it is the lowest-risk place to prove the integration.

Before you commit an afternoon: run composer require in a throwaway branch. If your app is on Laravel 9+, the illuminate/support constraint will very likely fail to resolve. In that case stop and read the last section of this document — a 150-line internal implementation is a cheaper path than a fork.

Implementation Patterns

1. Always read through a typed wrapper, never the facade directly

This is the single highest-leverage habit. The facade has no types, no defaults contract you control, and no place to hang documentation. Wrap it once:

// app/Support/Settings/Branding.php
namespace App\Support\Settings;

use Settings;

class Branding
{
    const KEY_NAME    = 'branding.name';
    const KEY_SUPPORT = 'branding.support_email';

    public static function name(): string
    {
        return (string) Settings::get(self::KEY_NAME, config('app.name'));
    }

    public static function supportEmail(): string
    {
        return (string) Settings::get(self::KEY_SUPPORT, config('mail.from.address'));
    }

    public static function all(): array
    {
        return [
            'name'          => self::name(),
            'support_email' => self::supportEmail(),
        ];
    }
}

Three wins: keys live in exactly one place, the fallback default is explicit rather than whatever the package happens to do, and replacing the package later means editing one directory. The callers never learn the package exists.

2. Config-first, database-second overlay

Do not move values out of config/ just because you now have a database. Keep the canonical default in a config file and treat the DB row as an override:

$fromAddress = Settings::get('mail.from_address') ?: config('mail.from.address');

This gives you a working application on a fresh clone with no seeding, makes the "what is this value supposed to be" question answerable from the repo, and means a corrupted settings row degrades to a sane value rather than null. Reserve the database strictly for values a non-developer genuinely needs to change at runtime.

3. Cache deliberately, but do not double-cache blindly

The package advertises caching. In day-to-day use what you actually want is a single application-level read of the whole settings table, cached and reused for the request or for a short TTL:

// app/Support/Settings/SettingsRepository.php
public function all(): array
{
    return Cache::remember('app.settings', 60, function () {
        return Settings::all();   // one query instead of N
    });
}

public function put(string $key, $value): void
{
    Settings::set($key, $value);
    Cache::forget('app.settings');
}

The N+1 pattern is the trap here: calling Settings::get() in a Blade partial that renders on every page turns into one query per call unless the package itself is caching. Read the source to find out which it is before optimising.

4. Admin write path: whitelist keys, validate per key

Never accept a key from the request body and pass it to set(). That is an arbitrary-write primitive.

// app/Http/Controllers/Admin/SettingsController.php
public function update(Request $request)
{
    $data = $request->validate([
        'branding.name'          => 'required|string|max:80',
        'branding.support_email' => 'required|email',
        'features.beta_banner'   => 'boolean',
    ]);

    foreach ($data as $key => $value) {
        Settings::set($key, is_bool($value) ? (int) $value : (string) $value);
    }

    Cache::forget('app.settings');

    return back()->with('status', 'Settings saved.');
}

Validate the keys you allow, not the keys the form sent. Cast booleans to 0/1 at the boundary — every value in this table is a string on the way back out.

5. Seed defaults so every environment is identical

// database/seeds/SettingsSeeder.php
foreach (config('settings.defaults', []) as $key => $value) {
    if (! Settings::get($key)) {
        Settings::set($key, (string) $value);
    }
}

Run it from your deploy script. It makes staging and production agree, and it means the admin screen renders with populated fields on day one instead of a blank form that overwrites values when submitted emptily.

6. Expose to views through a view composer

Do not let Blade call the facade. Inject the resolved array once per request:

View::composer('layouts.app', function ($view) {
    $view->with('settings', app(SettingsRepository::class)->all());
});

7. Test against your wrapper, not the package

Your test suite should assert Branding::name() returns the configured fallback and returns the override after a put(). Use RefreshDatabase plus the seeder. You are testing your contract, not the vendor's — that is what makes the package swappable.

Gotchas and Tips

The version constraint is the first wall. The last release is 2017 and the headline was Laravel 5.4 support. On Laravel 8+ you will likely hit an illuminate/support conflict. Run composer why-not efriandika/laravel-settings 1.2.5 before you plan anything; if it cannot resolve, the entire assessment is moot and you should port the ~150 lines of logic into app/ instead of forking a maintenance-mode package.

Manual provider registration is mandatory. On 5.4 there is no auto-discovery. A fresh environment that boots without the provider throws a Class 'Settings' not found at the first facade call — which may be deep inside a Blade template, giving you a confusing stack trace. Add the registration to your setup runbook, not just to your local config/app.php.

Everything comes back as a string. Settings::get('features.beta') returns "1", not true, and "0" is falsy but "" is also falsy while "false" is truthy. Cast at the wrapper boundary, once, and never again. Arrays must be json_encoded on write and json_decoded on read — the package does not do this for you.

Missing-key semantics are the #1 source of surprise. Find out empirically whether a miss returns null, false, or the default you passed, and whether passing a default causes a write. Two minutes in tinker saves an hour of debugging a page that renders blank in staging but fine locally because someone seeded the row by hand.

Caching invalidation is split across two systems. If you wrapped the package in Cache::remember, a direct Settings::set() anywhere in the codebase (an artisan command, a queue job, a colleague's controller) silently leaves your cached array stale until TTL expiry. The rule: all writes go through your repository's put(). Grep for Settings::set in code review — if it appears outside the repository class, reject the PR.

config:cache does not help you. The DB settings are not part of the compiled config, so clearing config cache on deploy will not refresh them, and clearing your settings cache will not refresh config. Two separate deploy steps. Put both in the deploy script and document why.

Long-running processes hold stale values. Under queue workers or Octane, a settings change mid-process will not be observed. Either keep TTL short and accept eventual consistency, or read settings fresh at the start of each job rather than injecting them into a singleton.

No tenancy, no namespacing, no scoping. There is one flat settings table. If you are multi-tenant, this package gives you a global bucket and nothing else — you would have to add a tenant_id column, override the repository, and re-implement every query. At that point write your own.

Table and model collisions. The migration creates a settings table and may bind a model. If your application already has a settings table or a Setting model, publishing the migration will surface as a duplicate-table error at deploy time, not at install time. Check database/migrations before publishing.

Booleans in forms. An unchecked HTML checkbox submits nothing, so a naive form handler leaves the old value 1 forever. Normalise in the controller: $request->boolean('features.beta_banner') or an explicit hidden 0 input.

Debugging checklist, in order: (1) select * from settings; in the DB — does the row exist and is the value what you think? (2) Settings::all() in tinker — does the facade see it? (3) Cache::get('app.settings') — is your layer stale? (4) config('app.debug') plus a fresh php artisan config:clear. In practice it is the cache layer nine times out of ten.

Extension points worth knowing: the published config file (cache toggles, model binding), the container binding for the underlying repository class — if the package binds an interface you can swap in your own implementation without touching call sites — and the published migration, which you should edit to add indexes on whatever keys you query directly. Treat the config file as the primary seam and the container binding as the escape hatch.

Do not put secrets in here. The settings table is readable by anyone with SQL access or an admin screen bug, and it is not integrated with Laravel's encrypted casting. API keys belong in environment variables or a secrets manager. Values that belong here: display names, copy text, support emails, feature toggles, page-size defaults.

Write down the day you adopt it. This package is in maintenance mode by every signal available — a 2017 release whose last change was a compatibility bump. Assign an owner, note the pinned version in your dependency review, and diarise a re-check at your next Laravel upgrade. The realistic outcome is that you replace it with thirty lines of your own code; the wrapper pattern in section 1 is what makes that a one-afternoon job rather than a refactor.

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.
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin