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

Filament Settings Laravel Package

wotz/filament-settings

wotz/filament-settings is a Filament plugin for managing Laravel application settings directly from your admin panel. Store settings in the database, edit them through a clean UI, and retrieve cached values in code without touching config files. Supports grouped settings for organized configuration.

View on GitHub
Deep Wiki
Context7

Getting Started

Install into an existing Laravel + Filament app:

composer require wotzebra/filament-settings
php artisan vendor:publish --tag="filament-settings-config"
php artisan vendor:publish --tag="filament-settings-migrations"
php artisan migrate

Register the plugin on the panel you want settings exposed in — typically app/Providers/Filament/AdminPanelProvider.php:

use Wotzebra\FilamentSettings\FilamentSettingsPlugin;

public function panel(Panel $panel): Panel
{
    return $panel
        // ...
        ->plugin(FilamentSettingsPlugin::make());
}

Where to look first. Because the package is small (single maintainer, 1 star, no dependents), read src/ before the README — start with the service provider (to see what config/migrations/views are published and which bindings are registered), then the page class the plugin mounts, then whatever model or repository persists values. The service provider tells you the whole surface area in under a minute.

First use case. The natural first job is a "Site Settings" page: site name, logo upload, contact email, footer text, and a handful of boolean feature toggles, editable by admins and read all over the front end. Build one page with a Filament form schema, save it, then confirm you can read the values back outside Filament (a Blade view or a tinker session) — that round trip is the whole value proposition, and confirming it early surfaces caching problems before you build five more pages on top.

php artisan tinker
>>> setting('site_name');   // or whatever accessor the package exposes

Implementation Patterns

One page per settings group. Rather than a single wall of fields, create a page per domain (GeneralSettings, MailSettings, BillingSettings). Filament's navigation grouping handles discovery, and each page becomes an independently permissionable unit. Check whether the package's base page class lets you set $navigationGroup, $navigationIcon, and $slug — most do, and if it does not, extend it and override.

Form schema as the source of truth. Define fields in the page's schema method using normal Filament components. Keep the field name identical to the settings key so persistence is a straight mapping:

TextInput::make('site_name')
    ->label('Site name')
    ->required()
    ->maxLength(255),

Toggle::make('maintenance_mode')
    ->label('Maintenance mode'),

FileUpload::make('logo')
    ->image()
    ->disk('public')
    ->directory('branding'),

State hydration and dehydration. The page needs to load persisted values into the form on mount and write them back on submit. Whatever hook the package provides (mount() filling $this->data, or a Form::fill()), keep that logic in the base page rather than duplicating it per page — a small abstract SettingsPage in your own app/Filament/Pages that extends the package page and implements load/save once will pay for itself by the third settings screen.

Reading settings in application code. Wrap the accessor in a thin helper of your own so you can swap the underlying package later without a repo-wide find-and-replace:

// app/Support/settings.php or a Settings facade
function setting(string $key, mixed $default = null): mixed
{
    return \Wotzebra\FilamentSettings\Facades\Settings::get($key, $default);
}

Use the default parameter aggressively in Blade (setting('footer_text', '© '.date('Y'))) — it makes fresh installs and test databases work without seeding.

Caching. Settings are read on nearly every request and written rarely; if the package does not cache by default, add a cache layer keyed by group and flush it in the save method. If it does cache, verify the flush happens on save and on delete, and that it is flushed in your test suite's setup.

Seeding defaults. Create a seeder that writes every key your app expects. This makes onboarding, CI, and migrate:fresh deterministic:

public function run(): void
{
    Settings::setMany([
        'site_name' => config('app.name'),
        'maintenance_mode' => false,
    ]);
}

Permissions. Settings pages are effectively privileged configuration, so restrict them. Either gate the page (public static function canAccess(): bool) or use Filament shield/policies if you already have them. Do not rely on navigation-hiding alone — a hidden page is still reachable by URL.

Multi-panel and tenant scope. If you run more than one panel, register the plugin only on the panels that should edit settings, and check whether the storage is global or scoped. For multi-tenant apps, decide deliberately whether settings are per-tenant (branding, contact info) or global (mail transport) — and if the package only supports one, layer your own tenant key column rather than fighting it.

Gotchas and Tips

Treat this as a small, unproven dependency. One maintainer, 1 star, zero dependents, no release history to speak of. Pin the exact version in composer.json, commit the published config and migrations to your repo, and be prepared to fork. If the settings surface is central to your app, isolate it behind the helper function above so replacement is a one-file change.

Publishing is not idempotent. Re-publishing after an upgrade will happily overwrite your edited config or add migrations you have already run. Publish once, then diff the package's stubs against your copies on every version bump.

Type casting on the way out. Values coming back from a key/value store are strings unless the package casts them. Toggle saving true and reading back "1" will break an if in a Blade directive that expected a boolean. Either normalise in your accessor (filter_var($v, FILTER_VALIDATE_BOOLEAN)) or store JSON and decode once, centrally.

File uploads need a real disk and URL. Storing an uploaded logo is only half the job — you also need Storage::disk('public')->url($path) in the view and a php artisan storage:link. If you deploy behind a CDN or to ephemeral containers, point the disk at S3 and never assume the local path is reachable. Also clean up the old file when a new one is uploaded, or your bucket grows forever.

Cache invalidation is the classic bug. A settings change that does not appear on the front end is almost always a stale cache or a queue worker holding an old config. Debug by reading the raw row from the database first — if the DB is correct and the page is stale, it is caching; if the DB is stale, it is the save path (mass assignment, a fillable guard, or a form field missing from the schema).

Form state vs. saved state. A common frustration: changing a field, clicking Save, seeing the success notification, and finding the old value on reload. That means the form was never dehydrated into the persisted payload. Log the array in the save method before writing; if the key is absent, the field name in the schema does not match what the persistence layer expects.

Arrays and repeaters. Repeaters and multi-selects need JSON columns (or serialised values). If the package ships a narrow key/value string column, either avoid complex components or add your own cast. Check the migration before designing the form.

Testing. Cover the save path with a Livewire test (livewire(GeneralSettings::class)->fillForm([...])->call('save')->assertHasNoFormErrors()) and assert the value via your accessor, not the model — that tests the whole round trip. Clear the settings cache in setUp() so tests do not leak values into each other.

Extension points to look for. The package's base page class (subclass it for shared load/save logic), the published config (defaults, cache TTL, disk), the model if it is publishable (add tenant columns, casts), and the plugin's ->make() fluent methods (navigation label, slug, group). If none of these exist, the package is likely too thin for anything beyond a single global settings page, and a hand-rolled Setting model with a Filament resource will cost you less long-term.

Debugging tip. When the settings page 500s after install, it is nearly always a missing migration or a config key pointing at a class that was renamed between versions. Run php artisan about and php artisan vendor:publish --list to confirm what is actually registered before reading the stack trace in detail.

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.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager