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

reworck/filament-settings

reworck/filament-settings provides a Filament admin interface for managing Laravel application settings. Store persistent, editable configuration values and access them throughout your app, making runtime settings easier to maintain without code changes.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and publish

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

If the package ships a config file, publish it too (--tag="filament-settings-config") before you go looking for behaviour in the source. As of 0.4.0 there is also a translatable save message, so check the service provider (or vendor/reworck/filament-settings/src) for a lang/translations publish tag and publish that if your panel isn't in English.

With a low star count and a release history that goes 0.3.0 (April 2023) → 0.4.0, treat the vendor/reworck/filament-settings/src directory as the real documentation — check the base page class, the Setting model, and the migration before building on top of it.

First look, in this order

  1. The published migration — this tells you the actual schema (key/value, group column, casts, timestamps) and therefore what the package can and can't store.
  2. The abstract/base settings page class — this is what you extend, and where mount()/save() hydration logic lives. As of 0.4.0 this is also where the page-title and save-message customisation hooks live; skim it after every bump, because it's the class most likely to change in a community PR.
  3. The retrieval side — either a setting() helper, a Settings facade, or Setting::get() on the model. Whichever exists, wrap it once (see below) and never call it raw from 40 Blade files.

First use case

Replace the usual config('app.name')-style hardcoding with an admin-editable "General" page: site name, contact email, maintenance toggle. Create one page class per settings group, register it on the panel, define the form, and read the values back through a small accessor class. That is the whole value proposition of this package — a Filament form bound to a key/value table — so prove that loop end-to-end before adding more groups.

namespace App\Filament\Pages\Settings;

use Filament\Forms;
use Filament\Forms\Form;
use Rework\FilamentSettings\Pages\SettingsPage;

class GeneralSettings extends SettingsPage
{
    protected static ?string $navigationIcon = 'heroicon-o-cog-6-tooth';
    protected static ?string $navigationGroup = 'Settings';
    protected static ?string $title = 'General';

    public function form(Form $form): Form
    {
        return $form->schema([
            Forms\Components\TextInput::make('site_name')->required(),
            Forms\Components\TextInput::make('contact_email')->email(),
            Forms\Components\Toggle::make('maintenance_mode'),
        ])->statePath('data');
    }
}

Register it in AdminPanelProvider::panel()->pages([...]) (or rely on discovery if enabled). The base class owns hydration and persistence; you own the schema. On 0.4.0, if you want the panel-facing title to differ from the navigation label (or to be translated per-locale), use the new title hook rather than hardcoding $title — but confirm the exact property/method name in the base class first, since it isn't documented anywhere but the diff.

Implementation Patterns

One page per domain, not one page for everything. GeneralSettings, MailSettings, IntegrationSettings, SeoDefaults. Groups map cleanly onto both the DB keys and the admin navigation, and they keep forms from becoming 60-field monsters that are impossible to review in a PR.

Namespace your keys. Store as general.site_name, mail.from_address, etc. Flat keys like name or email will collide the moment a second page is added, and debugging a collision after the fact is unpleasant.

Centralise reads behind one class. Do not scatter helper calls. A thin repository gives you casting, defaults, and a single place to swap out if the package is abandoned:

namespace App\Support;

use Rework\FilamentSettings\Models\Setting;

class Settings
{
    public static function get(string $key, mixed $default = null): mixed
    {
        return Setting::get($key) ?? config("settings.{$key}", $default);
    }

    public static function bool(string $key, bool $default = false): bool
    {
        return filter_var(self::get($key, $default), FILTER_VALIDATE_BOOL);
    }

    public static function array(string $key, array $default = []): array
    {
        $value = self::get($key);

        return is_string($value) ? (json_decode($value, true) ?: $default) : ($value ?? $default);
    }
}

The config() fallback is the single most useful line here: it lets you ship sane defaults in config/settings.php and have the admin UI override them, which makes fresh installs and tests deterministic.

Cast at the boundary, not at the call site. SQLite/MySQL will hand you "1" and "0" for toggles and a JSON string for repeaters. Do the filter_var/json_decode once in the repository so no view ever writes $settings->maintenance_mode === '1'.

Invalidate cache on save. Settings are read on nearly every request; cache them. Override the save path (or use the page's afterSave hook) to Cache::forget('settings.all') and repopulate, otherwise you will spend an afternoon wondering why a toggle "doesn't work" until php artisan optimize:clear.

Gate the pages. A settings page is an admin surface by definition. Add canAccess() or a policy so non-admins don't see the navigation item:

public static function canAccess(): bool
{
    return auth()->user()?->can('manage-settings') ?? false;
}

Translate the chrome, not just the form. 0.4.0 makes the save notification translatable. If you ship a non-English panel, don't stop at your field labels: publish/override the package's lang file so the "saved" toast matches the rest of the UI, and use the title hook to translate page titles per locale. Two lines of lang/es/settings.php prevent the one English string that makes an otherwise-localised admin look unfinished.

Seed defaults. A SettingsSeeder (or the config fallback above) means migrate:fresh --seed produces a working app rather than a form with empty required fields that nobody can save.

Feature flags are the sleeper use case. A features.* group plus Settings::bool('features.new_checkout') around a route or Blade block gives you deploy-time toggles without a flag service — cheap and genuinely useful day-to-day.

Testing. Use RefreshDatabase, seed the settings table, and clear the package's cache in setUp(). Assert through your repository, not the helper, so the tests survive a package swap. Add one test per settings page that fills the form, saves, and re-reads through the repository — that's the cheapest insurance against a future hydration regression in the base class.

Gotchas and Tips

  • Low activity, but not dead. 0.4.0 is the first release since 0.3.0 (April 2023), and the only change is a single community PR adding page-title customisation and a translatable save message. That's a heartbeat, not a maintenance guarantee: read the 0.3.0...0.4.0 diff before bumping, pin the exact version, and verify Filament 3 compatibility in composer.json. The schema is simple enough (settings table + form page) that a 100-line in-house replacement is a realistic escape hatch — design your accesses through a wrapper so that swap costs hours, not weeks.
  • A minor bump touched the base class you subclass. 0.4.0's changes land in SettingsPage. If you already have subclasses of it (e.g. a shared BaseSettingsPage for consistent navigation group/auth), merge carefully and re-test that the form still hydrates — a title-related property collision fails silently.
  • Don't read settings during application boot. Calling the helper in a service provider, a config/ file, or a middleware that runs before the DB is available will blow up during migrate, config:cache, or on a fresh clone. Guard with Schema::hasTable('settings') or resolve lazily.
  • Booleans and arrays come back as strings. Filament's Toggle writes 1/0; repeaters/tags write JSON. Untyped reads are the #1 source of "the toggle is always on" bug reports.
  • File uploads store paths, not files. A FileUpload field stores the disk path — you still need storage:link, a configured disk, and logic to delete the old file when it's replaced, or your storage bucket quietly grows forever.
  • Overriding mount() or save() without calling parent:: breaks hydration or persistence and produces a form that renders fine but silently never saves. If you need custom behaviour, hook after the parent call.
  • Multi-tenancy is not free. Settings are global by default. If two teams need different values, you are adding a team_id column and scoping every read — decide that before the table has data in it.
  • Publishing migrations can collide with an existing settings table from another package. Rename via config if the package supports it; otherwise check for conflicts first.
  • statePath('data') mismatches are a classic Filament footgun: schema keys must match what mount() fills, or every field appears blank while the DB is fully populated. If the form loads empty, log the hydrated state before the schema renders.
  • Deployment caching. If you cache settings, php artisan config:cache and optimize in your deploy script are the first suspects for "the change didn't apply in production". The new translated strings are cached too — a lang change needs a deploy that clears compiled views/config.
  • Extension points worth knowing: the base page class (subclass for shared behaviour across all your settings pages; also the hook point for titles and the save message as of 0.4.0), the Setting model (add casts/accessors rather than editing vendor files), the published lang files (override, don't edit in vendor/), and the migration (add columns there, not in a second migration, if you're forking).
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.
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums
rlacerda83/query-parser
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