ferdiunal/nova-settings
A Laravel Nova package for managing application settings from the Nova admin panel. It provides a settings UI and storage for editable configuration values, making runtime settings easy to view and update without code changes.
Confirm the baseline. You need Laravel + Laravel Nova already installed — this package is a Nova screen over Spatie's settings layer, so it is worth zero without Nova. Check the package's composer.json constraints against your Nova major version before you install; Nova majors move fast and the package is at v1.0.x with a thin maintainer bench.
composer require ferdiunal/nova-settings
Publish config and migration separately. Publish by tag so config changes are reviewable in a PR without dragging migrations along (and vice versa):
php artisan vendor:publish --provider="Ferdiunal\NovaSettings\NovaSettingsServiceProvider" --tag=nova-settings-config
php artisan vendor:publish --provider="Ferdiunal\NovaSettings\NovaSettingsServiceProvider" --tag=nova-settings-migrations
php artisan migrate
Read the published migration before running it. If your app already owns a settings table (another package, or your own legacy one), edit the migration now — renaming afterwards means a rename migration plus data copy.
Register the settings surface in Nova. Add the package's tool/page (or field) in app/Providers/NovaServiceProvider.php. Start with three scalar fields — a site name (text), a support email (text), and one boolean feature flag — so you can prove the full round trip: render → edit → save → read.
Prove the read path. Read the value back in a Blade view or a tinker session. Confirm the accessor against the published config and README rather than assuming — it is typically a settings('key') style helper or a Spatie settings class accessor. Do this before wiring it into business logic; a typo'd key silently returns your default and looks like a save bug.
Where to look first, in order: the published config (key registry, groups, cache defaults) → the migration (table shape, whether it's Spatie's group/name/payload layout) → the Nova registration (what actually renders). Those three answer 90% of day-one questions.
Treat config as the key registry. Declare every key, its group, type, and default in one config file. Then settings('mail.from') calls scattered across the codebase are readable against a manifest, and you can grep for orphaned keys during cleanup. This is the single highest-leverage habit with this package.
Always read with a default. Fresh environments, CI, and new tenants have no rows. Pass an explicit default at every call site (or rely on config-declared defaults) so a missing row degrades gracefully instead of throwing inside a request.
Cache reads; invalidate on write. Settings are read on nearly every request and written almost never — the textbook cache case. Verify whether the package caches internally and busts on save. If it does not, wrap reads:
$from = Cache::rememberForever('settings.mail.from', fn () => settings('mail.from', config('mail.from.address')));
and invalidate in the field's save path, an observer, or a model event. Unbounded per-request settings queries are the real performance trap here and they will not show up in dev.
Group by domain, not by one giant screen. Mail, branding, integrations, feature flags, pricing — one Nova group each. Navigable editing UI, and permissions are easier to reason about per group.
Read at the boundary, inject inward. Resolve settings in service providers (AppServiceProvider::boot() for mail/branding config overrides), feature-flag checks, or a thin Settings repository you own. Do not call the accessor deep inside Eloquent models — it makes tests slow and hides the coupling. Keeping reads behind your own service also makes swapping to spatie/laravel-settings, a config service, or a bespoke table a contained change later.
Custom field types. If a stakeholder needs a bespoke input (dependent dropdown, JSON blob with validation, prefilled value), subclass the package's Nova field and override resolve/fill rather than building a parallel storage layer. v1.0.12 exposing the current attribute value to the field is exactly the affordance that makes this practical — see the gotcha below about it being an internal API.
Testing. Use RefreshDatabase, seed only the settings rows the test depends on (or write them through the package's API), and flush the settings cache in setUp. Cached settings leaking between tests is the #1 source of "passes locally, fails in CI" in this setup.
Treat production-edited values as data, not code. They do not ship with a deploy. For anything that must be identical across environments (a flag that gates behavior), seed from config and treat the Nova editor as an override.
v1.0.12 is additive and internal. "Added current attribute value for internal use by the field" (PR #7, @joserick's first contribution) surfaces the field's current attribute during render/edit. No config or schema changes, no migration. Pin ^1.0 and take it on a patch bump — the only risk surface is custom field subclasses.
"Internal use" means internal. If you build a custom field on top of that attribute plumbing, expect it to move in a minor release. Depend on documented field methods where you can, and write a test that exercises your subclass so a future patch fails loudly instead of silently rendering stale values.
Cache staleness after out-of-band edits. Change a row in tinker, a seeder, or a data migration, and a cached value keeps serving the old data. Either go through the package's write path or explicitly flush after the change. This is the most common false "the package is broken" report.
Debug reads in this order: row exists in the table → cache key is fresh → the accessor's key string matches the config key exactly (typos silently return the default) → the Nova field actually saved (check updated_at). Skipping to step 3 costs people hours.
vendor:publish without a tag can overwrite customized config. Publish once, commit the result, and re-publish only when deliberately merging upstream — diff before accepting.
Secrets do not belong here. Integration API keys are tempting because the editor is convenient, but this is your application database with no rotation, KMS backing, or audited access beyond Nova's own gates. Store secrets in a vault and reference them from a settings field at most.
Multi-tenant scoping is yours to verify. Check what tenant isolation, if any, exists before designing around it. If it is absent, model tenant-aware settings as your own Eloquent model and reserve this package for genuinely global configuration.
Extension points beat forking. The package is small enough to read end-to-end in an afternoon. Before working around a limitation, skim the service provider and the field class — validation, grouping, and custom accessors are usually achievable via subclass or container binding, which keeps you on the upgrade path.
Assume replacement in 12–18 months. v1.0.x, one-to-two maintainers, tiny contributor base. Pin the version, watch the changelog, keep settings access behind your own boundary, and confirm you have a migration/export path for the settings data before you depend on it heavily.
How can I help you explore Laravel packages today?