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

Setting Laravel Package

akaunting/setting

Akaunting Setting is a Laravel package for persistent application settings. Store, retrieve, update, and forget key-value configuration in the database with caching and a simple facade/helper API, making runtime settings easy to manage across your app.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and publish in two commands:

composer require akaunting/setting
php artisan vendor:publish --provider="Akaunting\Setting\Provider"
php artisan migrate

Laravel's package discovery registers Akaunting\Setting\Provider and the Setting facade automatically. The publish step drops a config/setting.php and a migration that creates the settings table (key / value columns, unique on key).

Look first at:

  1. config/setting.php — table name and any store/driver options.
  2. The published migration — confirm the table name doesn't collide with anything you already have before running it.
  3. vendor/akaunting/setting/src/Setting.php — the whole API surface is small; read it once and you know the package.

First use case: application-wide preferences, not user preferences. Site name, default currency, timezone, pagination size, SMTP host, feature flags. Anything you'd otherwise hardcode in config/ but want an admin to change without a deploy.

// seed a default once, idempotently
if (! setting()->has('app.currency')) {
    setting()->set('app.currency', 'USD');
}

// read anywhere
$currency = setting('app.currency', 'USD');

That's the whole package. It is deliberately unambitious — a persistent key/value store with a helper. The assessment should be read with that in mind: it is a good fit for simple global settings and a poor fit for anything structured of per-user, audited, or secret.

Implementation Patterns

Helper vs. facade vs. injection. Three equivalent entry points, pick one per codebase and stick to it:

setting('app.name');                     // get with null default
setting('app.name', 'Acme');             // get with default
setting(['app.name' => 'Acme']);         // bulk set
setting()->all();                        // everything, as array

Setting::get('app.name', 'Acme');        // facade
Setting::set('app.name', 'Acme');
Setting::has('app.name');
Setting::forget('app.name');

The helper without arguments returns the underlying Setting instance, which is what makes setting()->set(...) work. Do not use setting('') as a "give me everything" shortcut — use all().

Bulk write from a settings form. The package's array form of setting() maps cleanly onto a validated request:

$data = $request->validate([
    'app.name'    => 'required|string|max:100',
    'app.currency'=> 'required|string|size:3',
    'mail.host'   => 'required|string',
]);

setting($data); // one call, dot-notated keys become nested structures

Cast on the way in as well as on the way out — validate booleans and integers explicitly, because the store is text-based and will happily give you "0" back where you stored false.

Wrap it in a typed settings class. The single highest-value thing you can do with this package:

class AppSettings
{
    public function currency(): string { return (string) setting('app.currency', 'USD'); }
    public function itemsPerPage(): int { return (int) setting('app.per_page', 25); }
    public function maintenance(): bool { return filter_var(setting('app.maintenance', false), FILTER_VALIDATE_BOOLEAN); }
    public function update(array $data): void { setting($data); }
}

Now type coercion lives in one place, your app doesn't depend on a helper function, and you can cache inside this class later without touching call sites.

Cache over the top. Every setting() call hits the store. For a handful of keys that's fine; for a boot-time loop over 40 keys, wrap it:

public function all(): array
{
    return Cache::rememberForever('settings', fn () => setting()->all());
}

public function update(array $data): void
{
    setting($data);
    Cache::forget('settings');
}

The package has no cache-busting hooks, so you own invalidation — including any script, seeder, or admin route that writes to the table directly.

Defaults on boot. Register a service provider that seeds missing defaults, so a fresh environment is usable without a manual admin step:

public function boot(): void
{
    foreach (config('app-defaults') as $key => $value) {
        if (! setting()->has($key)) {
            setting()->set($key, $value);
        }
    }
}

Guard it behind ! app()->runningInConsole() if you don't want it firing during every artisan command, or move it into an explicit settings:seed command invoked from your deploy pipeline.

Sharing with views. A small view composer beats calling setting() inside Blade templates — you get one query per request and a single place to add caching later.

Testing. Use RefreshDatabase and flush in setUp() so state doesn't leak between tests:

protected function setUp(): void
{
    parent::setUp();
    setting()->flush();
}

For unit tests that don't touch the DB, bind a fake implementation of your AppSettings class into the container instead of hitting the package at all.

Multi-tenancy. There is no tenant scoping. Either prefix every key (tenant.{id}.app.name) or point the package at a per-tenant connection in a scoped service provider. Prefixing is simpler and the package's dot notation supports it directly.

Admin UI. A controller with a validation ruleset, a setting($validated) call, and a redirect with a flash message is the canonical integration. Do not expose flush() or forget() from a web route without confirmation and authorization — they're destructive and unlogged.

Gotchas and Tips

It is global, not per-user. The most common misuse is storing user preferences here. There's no user scoping, no query API, no relationships. For per-user settings use a JSON column on users or a dedicated model — you'll want the query capability back within a week.

Values round-trip as text. Booleans, integers, and nulls are not preserved faithfully. setting('flag') may return "1", "true", or 1 depending on the store and how it was written. Always cast at the read site, never trust the PHP type. This is the #1 source of "why is if (setting('x')) always true" bugs.

Dot notation is structural. setting()->set('mail.host', 'x') creates a nested array, and setting('mail') returns the whole subtree. That means keys containing literal dots are ambiguous — keep key names dot-free unless you intend nesting. It also means renaming a key's namespace is a data migration, not a find-and-replace.

Do not store secrets. There is no encryption. API keys, SMTP passwords, and webhook secrets in the settings table end up in database dumps, backups, and any read-replica you hand to an analyst. Use encrypted casts on a real model, or a secrets manager, for those.

flush() and forget() are unguarded and unlogged. No events, no audit trail, no soft delete. If you need change history (and on a settings screen you eventually will), write your own wrapper that records old/new values before delegating.

Config cache is fine; data cache is not automatic. php artisan config:cache works normally — the package's config is a plain config file. But runtime values are read from the database on every call, so they are never stale and never cached for you. The flip side: any cache layer you add is yours to invalidate on set, forget, flush, seeders, and raw SQL.

Package activity is low. Last release was November 2023. Check Laravel and PHP compatibility against your target version before committing, and pin the version in composer.json. The API is small enough that if it ever needs a fork, the surface area is a day's work — which is the main thing in its favour.

Migration collisions. The published migration creates a table from config. Publish and edit the config before running migrate if settings is already taken in your schema; retrofitting the table name afterwards means renaming a live table.

Environment drift. Settings live in the database, so local, staging, and production diverge silently. New keys added to code that reads setting('x') without a default will be null in production and work fine locally where someone clicked a checkbox. Always pass a default at the read site, and ship an idempotent seeder for anything that must exist.

Debugging. dd(setting()->all()) beats guessing. Then check the actual table: SELECT * FROM settings WHERE key LIKE 'app.%'. If a value "isn't saving", confirm the key isn't being written through a path that bypasses your cache-invalidating wrapper, and confirm nothing else is calling flush() — a test suite hitting a shared database is the usual culprit.

Extension points are thin but sufficient. The Setting class is resolved from the container, so you can bind your own subclass or a wrapper contract in a service provider and swap the implementation without touching call sites. The published config exposes the table name. Beyond that, treat the package as a storage detail behind your own AppSettings class — that's the extension point that actually matters, and it's the one that lets you migrate off the package later without a rewrite.

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.
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console