metalinked/laravel-settings-kit
A Laravel settings kit for storing and managing application configuration in the database. Provides a simple API to define, retrieve, and update settings, making it easy to add admin-editable options and runtime configuration to Laravel apps.
This is the release that makes laravel-settings-kit safe to expose. v2.0.1 turns encrypted settings into write-only values at every boundary the package owns — panel(), the JSON endpoints, settings:export, settings:list — while get() in your own PHP code still returns the decrypted plaintext. If you are picking the package up now, start here:
encrypted up front — retrofitting encryption onto an existing stored value is the one migration that is genuinely painful.The mental model to hold: a setting has a value and a default, and the default can be inherited from a global scope. A user row overrides the global row; if the user has no row, they inherit. Encrypted settings follow the same inheritance rule — but as of v2.0.1, an inherited encrypted default is null outside your code.
Write-only secrets, read-only flags in the UI. The core pattern the release pushes you toward: never bind a form field to a secret's value. Drive the UI from the two new panel flags:
filled — whether any value exists (own or inherited). Use this for "configured / not configured" badges, the placeholder text ("•••••••• saved"), and empty-state logic.encrypted — whether the value is a secret. Use this to switch the input type, disable autocomplete, and hide the value entirely.The rule from the upgrade notes is worth repeating verbatim in your code review checklist: display filled, and only send a new value when the user actually types one. An untouched field must submit nothing — not null, not "".
Null is now a no-op, not a write. The JSON endpoints ignore null and empty values for encrypted settings, so round-tripping a panel payload back to the server no longer wipes a stored secret. This makes the naïve "serialize the whole panel, PATCH it back" SPA pattern survivable, but do not lean on it as your only protection — a client that sends a stale non-empty value will still overwrite. Treat the ignore-null behaviour as a safety net, not a design.
Deletion is explicit. Removing a secret is a deliberate DELETE /settings/me/{key}. Wire a "Clear" / "Remove" action next to the secret field, confirm it, and refresh the panel afterwards so filled flips back. Do not offer "clear the textbox and save" as a deletion path — it is silently a no-op, and your user will believe the secret is gone.
Keep decryption inside your own code. get() still returns plaintext, so the pattern is: resolve the setting server-side in a service or action, hand it to the HTTP client, and never let it reach a view, an API resource, a JSON column, a log line, or an exception message. The package has now drawn the line at its own boundary; everything past that line is yours.
Export and list are for humans, not pipelines. settings:export skips encrypted settings unless --with-secrets is passed. Default to the safe form in any backup or diff script, and treat --with-secrets as an incident-response tool that writes to a file you delete afterwards. settings:list printing (encrypted) is the sanity check you run first when someone claims a setting is "missing" — it is usually present and simply not printable.
Inheritance-aware tests. Assert the three states that matter for each encrypted setting: unset and inheriting from the global row, explicitly set on the model, and explicitly deleted (DELETE) falling back to inherited. Write one test that hits the JSON endpoint and asserts value and default are both null for an encrypted key — it is a one-line regression guard against exactly the class of bug this release fixes.
panel() or a JSON payload will now see null — including, as the notes call out, an inherited global secret surfacing as a model's default. That is the fix, not a bug, but it will look like one in a UI that was quietly leaking. Audit every view, Livewire component and SPA route that touches an encrypted key before you ship the upgrade.settings:export output changes shape. If a backup job, diff, or fixture compares export output, dropping encrypted rows will break it. Either add --with-secrets deliberately (and secure the artifact) or update the comparison to tolerate the absence.null and empty values; your form should still send neither. A "save" that writes "" into a non-encrypted field behaves normally, so the bug hides until someone applies the same form logic to a secret. Use distinct request shapes for "unchanged" and "set".null default is ambiguous. For encrypted settings, default: null means "hidden", not "no default configured". If your UI distinguishes inherited from explicit values, use filled plus your own knowledge of which keys are encrypted — do not infer it from the payload.encrypted in the config, was --with-secrets omitted, is the row present (via settings:list showing (encrypted)), and did the client actually send a non-empty value. Four distinct causes producing one identical symptom.panel(). It is the single place that assembles what the UI sees, so any new derived flag, label, or grouping belongs there rather than in three separate Blade templates and a SPA. Read its implementation before adding a field — you are working against a documented contract now that filled and encrypted are part of it.filled/encrypted.How can I help you explore Laravel packages today?