padosoft/laravel-settings
Laravel Settings gives your application a simple, persistent key-value store for configuration. Define settings in the database, read and update them through a clean API, and rely on caching so values load fast without extra queries. Settings stay editable at runtime, keeping configuration out of co...
This release is primarily an upgrade task, not a greenfield onboarding, so start with the upgrade path before writing any new code.
UPGRADE-7.0.md first. The release notes deliberately defer the full breaking-change list to that file. Everything below assumes you have read it.settings.value is widened to TEXT. An pdsenc:v1:<kid>:<payload> envelope will not fit in VARCHAR(255), so skipping this migration will truncate or reject encrypted values.APP_KEY. Populate the current key plus any previous keys (see config/settings.php / the keyring config referenced in the upgrade guide). Back this up — it is now the single source of truth for decryptability.encrypted_keys. Only settings listed there are encrypted. If you have never populated encrypted_keys, you are functionally unaffected by this release.encryption-audit (dry-run by default). It reports whether allowlisted keys are stored as envelopes or plaintext, without printing values or lengths.First use case: store a third-party API token. Add the key to encrypted_keys, write it through SettingsManager::set() (or via the Eloquent model — both paths now converge on SettingsCipher), and read it back through the normal accessor. You get plaintext in PHP, an envelope in the DB, and the same envelope in the cache.
Where to look first: Encryption\SettingsCipher (the single encryption point), the new encryption-* commands, and Exceptions\DecryptException.
Write through either path — they are finally equivalent. The headline fix is that the Model mutator and SettingsManager::set() previously produced incompatible ciphertext (one layer vs. two), so Eloquent writes broke cache-cold reads. In 7.0 both route through SettingsCipher, so save() on the model and setAndStore() are interchangeable. Prefer the Manager API in application code for consistency, but you no longer need to police how other code writes settings.
Treat plaintext as the application-level contract. Validation rules and casts are now applied to the decrypted value rather than the ciphertext. Rules like string|max:255 on an encrypted setting now behave the way you always expected. Casts likewise operate on plaintext.
Rely on cache warm/cold parity. DB and cache contain the same raw envelope. You can stop special-casing cache clears after writes, and you can stop worrying that a cold cache will surface different behaviour than a warm one.
Rotation is a two-phase, keyring-driven operation:
previous. Both remain decryptable.encryption-rotate as a dry run to see the scope, then apply it (see UPGRADE-7.0.md for the exact apply flag).encryption-audit to confirm no values remain under the old kid.previous entry only after the audit is clean.The command is idempotent, so re-running it is safe and is a reasonable thing to do in a deploy pipeline.
Migrating existing plaintext into encrypted storage follows the same shape: encryption-migrate dry-run → review → apply → encryption-audit. Because the release is fail-closed, any allowlisted key still holding plaintext will throw once 7.0 is live, so run this migration before or as part of the deploy that ships 7.0.
Handle failures narrowly. All encryption exceptions extend Exceptions\DecryptException. If you already catch that type, your existing handlers keep working — you do not need to enumerate the new subclasses. Catching it around settings reads is the right integration point for degraded-mode behaviour (e.g. fall back to a read-only integration, surface a config error page).
Use redaction as a safety net, not a feature. toArray() / toJson() now redact encrypted values, and logging is scrubbed. That means it is now safe to accidentally Log::info($settings) or return a settings model from a debug endpoint. It also means array-based exports will not contain the secret — fetch the attribute directly when you actually need the value.
In tests and CI, run encryption-audit as an assertion step. Its output is designed to be loggable (no values, no lengths), so it can go straight into CI logs. Pair it with a check that the keyring is present in the environment.
TEXT migration is not optional. This is the most likely cause of a "works locally, explodes in prod" failure, because local SQLite is permissive about column width.APP_KEY rotation no longer affects settings encryption — and that cuts both ways. Previously a leaked APP_KEY compromised encrypted settings; now it does not. But losing the keyring means permanently losing the data. There is no APP_KEY-based recovery path. Store the keyring in your secret manager alongside the database backups.encryption-audit against production before rolling out 7.0, and sequence encryption-migrate accordingly. The old silent fallback to the default value is gone — which means bugs that used to be quiet misconfigurations are now loud exceptions.previous keys until the audit says you can drop them. Removing a previous key mid-rotation orphans every value still encrypted under that kid. The two-phase flow exists precisely so this window is observable.encryption-rotate and assumes it did something will silently do nothing. Check the upgrade guide for the apply flag and verify via encryption-audit, not via exit code alone.pdsenc:v1:... instead of a usable value. Audit cache consumers.encryptString() replaced serialization. The envelope is now a versioned, string-encrypted payload rather than a serialized structure. If you had tooling that inspected or migrated ciphertext by unserialize()-ing it, that tooling is dead and needs rewriting against the envelope format.echo statements from accessors. Rare, but if any test or console command depended on that output appearing, it will now be missing.encryption-audit intentionally never prints values or lengths. Do not expect it to tell you what is stored — only whether the storage shape is correct. For a specific value, decrypt it in tinker through the normal accessor and accept that it will be redacted in toArray().Encryption\SettingsCipher is the single seam for all encryption. If you need custom key resolution or envelope handling, that is the class to decorate or bind — not the Model mutator and not SettingsManager::set(), which is exactly the split that caused the 6.x breakage.How can I help you explore Laravel packages today?