outl1ne/nova-settings
outl1ne/nova-settings is a Laravel Nova package for managing application settings directly from the Nova admin panel. Define settings pages with custom fields and groups, persist values to the database, and keep configuration editable without code changes.
Install the package and publish its migration/config:
composer require outl1ne/nova-settings
php artisan vendor:publish --provider="Outl1ne\NovaSettings\NovaSettingsServiceProvider"
php artisan migrate
Register the tool in app/Providers/NovaServiceProvider.php:
public function tools(): array
{
return [
\Outl1ne\NovaSettings\NovaSettings::make(),
];
}
Register your first fields in the same provider's boot() method. This is the file you will live in day-to-day:
use Outl1ne\NovaSettings\NovaSettings;
use Laravel\Nova\Fields\Text;
use Laravel\Nova\Fields\Textarea;
public function boot(): void
{
parent::boot();
NovaSettings::addSettingsFields([
Text::make('Site name', 'site_name')->rules('required'),
Textarea::make('Footer text', 'footer_text'),
]);
}
Read values anywhere in your app with the helper:
$siteName = nova_get_setting('site_name', config('app.name'));
$footer = nova_get_setting('footer_text');
// Everything at once
$settings = nova_get_settings();
First practical use case: move hard-coded marketing copy, contact emails, social URLs, API keys for non-sensitive integrations, and simple feature flags out of Blade/controllers and into a Nova-editable page. Open /nova/settings as an admin and confirm the values persist.
Group related fields with Panels. The settings page is a single form; use Laravel\Nova\Panel to keep it navigable as it grows.
NovaSettings::addSettingsFields([
Panel::make('General', [
Text::make('Site name', 'site_name'),
Image::make('Logo', 'logo'),
]),
Panel::make('Contact', [
Text::make('Support email', 'support_email'),
Text::make('Phone', 'support_phone'),
]),
]);
Keep field definitions out of the Nova tool class. Put them in a dedicated service provider (e.g. SettingsServiceProvider) or a config/settings.php schema that your provider loops over. This keeps NovaServiceProvider from becoming a dumping ground and makes it easy to conditionally register fields per environment or per tenant.
Always pass defaults when reading. A setting that has never been saved returns null; treat the getter default as the source of truth for fresh installs.
$perPage = (int) nova_get_setting('per_page', 25);
$flags = (array) nova_get_setting('feature_flags', []);
Store structured data deliberately. KeyValue, Boolean, Select, and Date fields all serialize back through the getter, but the returned PHP type depends on how Nova cast the input. Cast explicitly at the read site ((bool), (int), json_decode) rather than assuming. For arrays of links or nav items, KeyValue is usually more maintainable than multiple flat fields.
Prefer Markdown fields for long-form copy. Since 6.0.2 the settings form renders a live preview for Laravel\Nova\Fields\Markdown fields, so editors can validate headings, links, and lists without leaving /nova/settings. Use it for footer copy, announcement banners, and legal blurbs instead of a raw Textarea:
Markdown::make('Footer text', 'footer_text')
->help('Markdown supported — preview renders as you type.'),
The stored value is the raw Markdown string, so render it at the read site (see gotchas below).
Use settings as configuration inputs, not business data. Good candidates: feature toggles, default pagination, contact endpoints, allowed file types, announcement banners. Bad candidates: records that need relationships, audit trails, or per-user overrides — those belong in their own tables/resources.
Write programmatically sparingly. For seeders, onboarding flows, or admin actions outside Nova, set values through the facade/model rather than inserting rows by hand. This keeps any package-level casting/caching consistent:
NovaSettings::setSettings([
'site_name' => 'Acme',
'support_email'=> '[email protected]',
]);
Use settings to seed Nova resource defaults. A common pattern is to read a setting inside a resource's fields() or default callback so admins can change global defaults without a deploy:
Text::make('Locale')->default(fn () => nova_get_setting('default_locale', 'en')),
Gate the tool. By default any Nova user can open the settings page. Restrict it in NovaServiceProvider (e.g. NovaSettings::make()->canSee(fn ($request) => $request->user()->isAdmin())) or via a policy if you have non-admin Nova users. Settings often contain integration credentials — treat the page as admin-only.
Resolve files/images through storage. Image/File fields store a disk path. When rendering, resolve it with Storage::disk(...)->url($path) rather than concatenating URLs, especially if you use S3 or a non-default disk.
Cache at the call site if you read hot settings frequently. The package reads from the database. If a layout partial calls nova_get_setting() repeatedly, wrap it in a small cached helper (Cache::remember('settings.site_name', ...)) and clear on save, or memoize per-request in a singleton.
addSettingsFields() must run on every request that renders or saves the settings page. Registering inside the tool class or a controller will produce an empty form or lose fields on save.->attribute() will collide — last registration wins. Prefix with a domain (billing_, seo_) to avoid accidental overwrites across providers.nova_settings and the new field starts empty. Write a small migration or updateOrInsert to move values when renaming.nova_get_setting() and the table must exist. Do not call the helper from an early service provider, config file, or a migration that runs before the package migration — you will hit "table not found". Guard or defer to after boot.Boolean may save false or null depending on Nova version; a Text field saved empty may be '' rather than null. Normalize in your accessor.Str::markdown($value) (or a CommonMark parser) and sanitize HTML before echoing it into Blade with {!! !!} — untrusted editor input otherwise becomes stored XSS. Never assume the preview implies sanitization.vendor:publish produces nothing, list tags with php artisan vendor:publish and check the package's config/nova-settings.php for the actual table name and publish tags instead of guessing.->rules() to fields you care about (emails, URLs, required values). Invalid settings saved silently become production bugs; the form is your only guardrail.->canSee().config/app.php, confirm the migration ran (nova_settings table exists and has rows), confirm the field attribute matches the key you pass to nova_get_setting(), and check storage/logs/laravel.log for Nova field exceptions during save.How can I help you explore Laravel packages today?