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

stepanenko3/nova-settings

A Laravel Nova tool for managing application settings from your admin panel. Define settings in code, edit them in Nova, and read values through a simple helper, keeping configuration editable without redeploys or env file changes. Useful for site options and feature flags.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and wire it up

composer require stepanenko3/nova-settings
php artisan vendor:publish --provider="Stepanenko3\NovaSettings\ToolServiceProvider"

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

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

Where to look first — in this order

  1. config/nova-settings.php — this file is the package. Every group, every field, every authorization rule and every value the UI can edit lives here. There is no migration, no model, no custom resource to learn. If you understand this config, you understand the tool.
  2. vendor/stepanenko3/nova-settings/src/ — it's a small codebase. Read it end to end before your first deploy. You need four answers and only the source gives them reliably: whether groups/fields can be registered at runtime, whether there's a save hook, how the tool authorizes access, and whether the v1.1.2 read cache exposes a public flush.
  3. .env — the storage layer. Every save is a read-mutate-rewrite of this file. Treat it as a data store you happen to also edit by hand.

Minimal working config

// config/nova-settings.php
return [
    'groups' => [
        [
            'name'   => 'Mail',
            'slug'   => 'mail',
            'icon'   => 'mail',
            'fields' => [
                \Laravel\Nova\Fields\Text::make('MAIL_FROM_ADDRESS')
                    ->rules('required', 'email'),
                \Laravel\Nova\Fields\Text::make('MAIL_FROM_NAME')
                    ->rules('required'),
                \Laravel\Nova\Fields\Select::make('MAIL_MAILER')
                    ->options(['smtp' => 'SMTP', 'log' => 'Log', 'ses' => 'SES']),
            ],
        ],
    ],
];

First use case: pick something boring

Open with a low-blast-radius ops group — a support inbox address, a mail-from name, an outbound webhook URL, a marketing feature flag. These are values ops genuinely wants to change without a deploy, are not read during container bootstrap, and fail in a recoverable way ("one wrong email sent") rather than catastrophically ("app won't boot").

Do not start with APP_KEY, APP_ENV, APP_DEBUG, DB_*, QUEUE_CONNECTION, or LOG_CHANNEL. Those are read before your config or are cryptographically load-bearing.

Verify the full loop before adding a second group

  1. Change the value in the Nova UI and save.
  2. grep MAIL_FROM_ADDRESS .env — did the file get rewritten, and did quoting survive?
  3. php artisan tinker --execute="dump(env('MAIL_FROM_ADDRESS'));" — this works because Artisan boots without a cached config.
  4. If step 3 is stale, you have a config-cache problem. If step 2 is stale, you have a save or filesystem-permission problem.
  5. Because v1.1.2 added a read cache, repeat step 3 from a second long-lived process (a queue worker) to confirm you're seeing the file, not a warm in-process cache.

Getting this five-step loop green is the whole onboarding. Everything after that is repetition.

Implementation Patterns

The one rule that decides whether this tool is useful: never call env() outside config/*.php.

Once php artisan config:cache runs — which it does in every serious production deploy — env() returns null everywhere except config files. The package's writes become invisible to your application unless you read them back through config():

// config/services.php — the only place env() belongs
'acme' => [
    'key'      => env('ACME_API_KEY'),
    'endpoint' => env('ACME_ENDPOINT', 'https://api.acme.test'),
],

// everywhere else in the app
$key = config('services.acme.key');

If you skip this, the tool will appear to work in the UI and appear to do nothing in the app. Decide now how the config cache gets invalidated after a save — a deploy hook, a queued job, an Artisan call in your save path, or simply not enabling the tool where config:cache is active.

Organise groups by blast radius, not by vendor.

Mail, Payments, Features, Integrations reads correctly on the Nova sidebar and answers the admin's real question: "is it safe for me to touch this?" Grouping by integration vendor pushes that judgment onto the person least equipped to make it.

Lean on the native field API — that's the entire value proposition.

Boolean::make('FEATURE_NEW_CHECKOUT')
    ->trueValue('true')
    ->falseValue('false')
    ->help('Toggles the new checkout flow for all users.'),

Password::make('STRIPE_SECRET')
    ->rules('required', 'starts_with:sk_')
    ->help('Write-only. Copy from the Stripe dashboard.'),

Textarea::make('MAINTENANCE_MESSAGE')
    ->rules('max:500'),

Text::make('SUPPORT_EMAIL')
    ->rules('required', 'email')
    ->help('Shown on the contact page and used as Reply-To.'),

Because these are genuine Nova fields, ->rules(), ->help(), ->options(), ->readonly(), and conditional display all behave exactly as documented. You write no per-field integration code. Spend your effort on validation rules instead — they are your only guardrail against .env-breaking input.

Gate the tool itself, not just the fields.

public function tools()
{
    return array_filter([
        $this->canEditSettings() ? new \Stepanenko3\NovaSettings\NovaSettings() : null,
    ]);
}

protected function canEditSettings(): bool
{
    return ! app()->environment('production')
        && Gate::allows('manage-settings');
}

The pattern that scales across a team: the Nova UI is a convenient editor for local and staging, and production values are promoted through your normal config pipeline (Vault, SSM, k8s ConfigMap). Staging becomes the "review the diff before it ships" surface, which is a genuinely useful workflow even if production stays read-only.

Treat secrets as write-only, and prove it.

For any Password field, do the explicit round-trip test: edit a neighbouring field, save, then grep the secret. A ******** placeholder being written back over the real value is the classic data-loss bug in every .env editor, and it will never surface in casual clicking. Test it once per upgrade.

Keep .env.example honest.

Add a CI check or a tiny Artisan command that diffs keys declared in config/nova-settings.php against .env.example. Keys that exist only in one environment's .env are the single biggest source of "works on staging, breaks in prod."

Back up before the write, log after it.

A timestamped copy of .env into storage/ on save costs nothing and has saved teams. Pair it with spatie/laravel-activitylog or the package's own save hook so every settings change has an author and a timestamp — environment edits are exactly what comes up in an incident review six weeks later.

Promote through environments deliberately.

If you're using staging as the review surface, keep a small script that diffs staging .env against .env.example and against the declared field list, so promotion is a reviewed diff rather than a copy-paste. Manual .env surgery across environments is where this tool's convenience curdles.

Pin exactly, upgrade deliberately.

"stepanenko3/nova-settings": "1.1.2"

v1.1.2 (PR #16, cache-hit optimisation) is a pure internal performance change: no new features, no breaking changes, no config surface change. It doesn't alter the integration contract. But caching patches change when you observe a value rather than whether it was written, and that class of bug only shows up under concurrency or in long-running processes. After any bump, smoke-test on staging: save a value, then read it from a fresh Artisan command, a queue worker, and the UI.

Gotchas and Tips

Config cache is the headline trap. Symptom: the value is clearly right in .env, the Nova UI shows it, but config('...') is stale and the app behaves as before. Fix by rebuilding config cache — but never inside an FPM request (php artisan config:clear there is slow and racy). Do it in a deploy step, or don't run the tool where config:cache is used at all.

There are now two caches, not one. Since v1.1.2 you have a package-level read cache and Laravel's config cache. Triage in this order and you'll localise any stale value in under a minute: grep the file (source of truth) → fresh php artisan tinker (bypasses config cache) → your web/queue process. If the package cache is backed by a shared store, a deploy or a second node can flush it for everyone mid-edit.

.env is not a database, and dotenv has rules. Values containing spaces, #, ", ', $, or newlines must be quoted. Double quotes interpolate ${VAR}. My #1 App silently truncates at the #. Test hostile input before trusting the tool: App #1, "quoted", pass$word, and a value ending in a backslash. If the package doesn't quote for you, constrain the field: ->rules('regex:/^[^#"\'\$\n]+$/').

Booleans are strings. .env has no boolean type. Native Boolean::make() yields 1/0, and (bool) "false" is true — a genuinely nasty bug. Set trueValue('true') / falseValue('false') explicitly, and read back with filter_var(config('...'), FILTER_VALIDATE_BOOLEAN). Grep .env after toggling to confirm what actually got written.

Permissions and containers. The PHP-FPM user needs write access to .env. In Docker/K8s, .env frequently doesn't exist (vars are injected by the platform) or the filesystem is read-only — the tool will fail, or worse, create a .env that shadows your injected values. Guard with app()->environment('local') or mount a writable path.

Some keys must never be editable here. APP_KEY rotation invalidates encrypted columns, sessions, and password-reset tokens. APP_ENV, APP_DEBUG, DB_*, LOG_CHANNEL are read during bootstrap. Either exclude them from the config entirely or render them ->readonly() so they're visible but not writable — visibility without writability is usually the right compromise.

Long-running processes don't see changes. Queue workers, Horizon supervisors, Octane workers, and the scheduler cache config at boot. After an edit: php artisan queue:restart, and octane:reload where applicable. Put this next to the Save button in your runbook. The 1.1.2 cache doesn't fix this — a warm in-process cache actually widens the window until the worker restarts.

Last-write-wins concurrency. These tools read the whole file, mutate one key, and write it back. Two admins saving simultaneously — or an admin saving while a deploy writes .env — silently loses one change. Keep .env writes out of the deploy pipeline while the tool is enabled, and back up before every write.

Orphaned keys accumulate. Values written by hand into .env with no declared field survive the rewrite but are invisible in the UI. Fine in principle, messy after a year. Reconcile config against .env periodically.

Four things to check in src/ before you integrate deeply — they decide whether you integrate cleanly or end up forking: can groups/fields be registered at runtime or only from config; is there a save event or failure hook for cache invalidation and activity logging; does the tool respect Nova::tools() authorization or does it need its own gate; and — new in 1.1.2 — does the read cache expose a public flush you can call from your own save hook.

Tests that actually catch the real failures. In Pest/PHPUnit, assert both the .env mutation and the resulting config() value; add a round-trip test with #, spaces, and quotes in the value; and add a test that saves a value then re-resolves it from a fresh container instance, so a stale package-level cache fails CI instead of production. Those three tests cover the majority of ways this class of package breaks.

Package risk stands regardless of the patch. Low stars, zero dependents, a single maintainer, and a Nova 4 vs 5 field-API constraint you must verify yourself against your installed Nova version. The good news is the contract is narrow — "write env vars" — so the fallback is contained: a ~150-line custom Nova tool, or spatie/laravel-settings layered over config, replaces it without touching application code. Pin it, read src/, keep the escape hatch open, and don't let it become load-bearing for anything beyond non-bootstrap ops values.

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.
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons