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

Nova Settings Laravel Package

optimistdigital/nova-settings

Nova Settings adds a dedicated settings tool to Laravel Nova for managing database-backed configuration. Define settings with familiar Nova fields, edit them through a clean UI, and access stored values from your application. A practical way to make app settings editable without code changes or rede...

View on GitHub
Deep Wiki
Context7

Getting Started

Install, publish, and migrate:

composer require optimistdigital/nova-settings
php artisan vendor:publish --provider="OptimistDigital\NovaSettings\NovaSettingsServiceProvider"
php artisan migrate

Publishing produces a config file (config/nova-settings.php) and a migration creating the nova_settings table (id, key, value JSON, timestamps).

Register the tool in app/Providers/NovaServiceProvider.php:

public function tools()
{
    return [
        new \OptimistDigital\NovaSettings\NovaSettings,
    ];
}

Then declare the fields you want to edit. This is the single most important integration point — fields are registered statically, usually in a dedicated service provider's boot():

use OptimistDigital\NovaSettings\NovaSettings;
use Laravel\Nova\Fields\Text;
use Laravel\Nova\Fields\Boolean;
use Laravel\Nova\Fields\Image;

public function boot()
{
    NovaSettings::addSettingsFields([
        Text::make('Site name', 'site_name')->default('My App')->rules('required'),
        Text::make('Support email', 'support_email')->rules('email'),
        Boolean::make('Maintenance mode', 'maintenance_mode'),
        Image::make('Logo', 'logo')->disk('public')->path('branding'),
    ]);
}

Read values anywhere in the app:

$name = nova_get_setting('site_name', 'My App');
$all  = nova_get_settings(); // keyed array of every stored value

The first practical use case is almost always brand/marketing/support copy — site name, logo, support email, social URLs, feature toggles — things ops or marketing want to change without a deploy.

Where to look first: the generated config file (table name, can_see/can_update, dependency reload), then the Nova sidebar's "Settings" page to confirm fields render correctly.

Implementation Patterns

Keep registration in one place. Create app/Providers/SettingsServiceProvider.php (or a Settings class with a boot()), and call NovaSettings::addSettingsFields() there rather than scattering calls across packages. This gives you one file to diff when a setting changes and lets you conditionally register fields per environment:

if (app()->isProduction()) {
    NovaSettings::addSettingsFields([Boolean::make('Beta access', 'beta')]);
}

Group by domain with panels. The tool renders native Nova fields, so panel grouping keeps a growing settings page usable:

NovaSettings::addSettingsFields([
    Panel::make('General', [/* ... */]),
    Panel::make('Email', [/* ... */]),
    Panel::make('Integrations', [/* ... */]),
]);

Wrap access in a typed helper. Raw nova_get_setting() returns whatever was JSON-decoded from the DB. Centralize keys and defaults so callers never pass string literals:

class Settings
{
    public static function siteName(): string
    {
        return (string) nova_get_setting('site_name', config('app.name'));
    }

    public static function maintenance(): bool
    {
        return (bool) nova_get_setting('maintenance_mode', false);
    }
}

Then use Settings::maintenance() in middleware, Settings::siteName() in Blade, etc.

Apply settings at boot for config overrides. Common pattern for mail/queue/branding defaults:

public function boot()
{
    if (Schema::hasTable('nova_settings')) {
        config([
            'mail.from.address' => nova_get_setting('support_email', config('mail.from.address')),
            'mail.from.name'    => nova_get_setting('site_name', config('app.name')),
        ]);
    }
}

Guard with a table-existence check so php artisan migrate on a fresh DB doesn't explode.

Use Nova's richer fields for structured data. Repeater, KeyValue, Select, Markdown, File/Image, and Color all work and store their native JSON structures. This is where the package earns its keep — you get the same editing UX as resources without writing a CRUD resource.

NovaSettings::addSettingsFields([
    KeyValue::make('Social links', 'social_links'),
    Repeater::make('Footer columns', 'footer_columns')->fields([
        Text::make('Heading'),
        Textarea::make('Body'),
    ]),
    Markdown::make('Terms of service', 'terms_md'),
]);

nova_get_setting('social_links') then returns an associative array. As of 6.0.2, Markdown fields render Nova's live preview toggle inside the settings form — useful for long-form legal/onboarding copy stored as settings, since non-developers can now preview formatting before saving. Pin >=6.0.2 if you rely on it.

Validate at save time. Any ->rules() on the field is enforced when the settings form is submitted — reuse the same rules you'd put on a resource.

Authorization via config closures. Restrict the whole page rather than individual fields:

'can_see' => fn ($request) => $request->user()?->isAdmin(),
'can_update' => fn ($request) => $request->user()?->can('manage settings'),

Dependency-driven UI. Fields using ->dependsOn() work, and reload_page_on_dependencies_update in config controls whether the page reloads after save so newly visible fields appear. Leave it on unless you have a reason not to.

Seeding for reproducible environments. Since defaults live on the field, nova_get_setting($key, $default) is the source of truth when nothing is stored. For local/staging parity you can alternatively seed the nova_settings table directly.

Gotchas and Tips

  • Closures in config break config:cache. Laravel can't serialize closures, so can_see/can_update closures will fail under php artisan config:cache. Use invokable classes or a gate check instead if you cache config in production.
  • Keys are global and flat. Two providers registering 'email' will silently collide — the last write wins. Prefix keys per domain (billing_email, marketing_email). There is no dot-notation nesting on read; store arrays via KeyValue/Repeater and index them yourself.
  • Boolean false vs. default. nova_get_setting('feature', true) returns false only if false was explicitly saved. A never-saved key returns true because no row exists. Cast explicitly and pass defaults for every read.
  • ->default() renders the initial value but does not persist it. Any code path calling nova_get_setting() without a second argument can still get null before someone opens the settings page and hits Save.
  • File and Image fields store a path, not a URL. Resolve with Storage::disk($disk)->url($path) at the call site; don't assume the value is a fully qualified URL.
  • Renaming a field key orphans the old row. The package does not garbage-collect removed keys. If you rename site_name → app_name, migrate the data manually or read from both keys during the transition.
  • Deleting a field doesn't delete its value. Same issue from the other direction — stale rows accrue over time. A periodic whereNotIn('key', $registeredKeys)->delete() cleanup is worthwhile once you have dozens of settings.
  • Every read can hit the database. Depending on version, values may not be cached. If you call nova_get_setting() in a hot loop or a Blade partial rendered per item, memoize the whole set once: Cache::rememberForever('settings', fn () => nova_get_settings()) and flush on save (or on a short TTL).
  • No multi-tenancy. Settings are global. If you need per-tenant config, either key by tenant ID ("tenant_{$id}_name") and manage lifecycle yourself, or reach for spatie/laravel-settings instead.
  • Don't store secrets here without locking it down. Anyone with can_see can read API keys stored in a Text field. Prefer .env for credentials; if you must, restrict with can_update/can_see, mark the field ->hideFromIndex()-like via password input type, and audit access.
  • Nova version compatibility. Confirm support for your Nova major version (4 vs 5) — the package tracks Nova releases closely but lags occasionally. Pin the package version alongside Nova.
  • The package was renamed from optimistdigital/nova-settings to outl1ne/nova-settings (namespace OptimistDigital\ → Outl1ne\). Fresh installs may resolve either name; check your composer.json and use statements before upgrading, and republish config/migration tags if the namespace changed.
  • Tests need the migration plus field registration. Add RefreshDatabase and register fields in a test service provider; otherwise nova_get_setting() returns nulls and the tool route 404s.
  • Debugging a "setting didn't save": check the nova_settings row directly first, then field rules() (a failed validation silently prevents save), then the config closure (can_update returning false hides the Save button). The DB is the fastest triage point.
  • Markdown preview is cosmetic only (6.0.2+). The preview toggle renders client-side; the stored value is still the raw Markdown string. If you render it yourself, sanitize with a Markdown/HTML purifier — the package doesn't sanitize on save.
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.
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect