Devani configuration has two layers, and one signal that changes behavior between self-hosted and managed installs.
Layer 1: the environment (devani/config.php)
config.php reads DEVANI_* environment variables via devani_env() (checking getenv, $_SERVER, and $_ENV). Keys include:
DEVANI_TONTA_API_KEY DEVANI_TONTA_UPLOAD_ENDPOINT
DEVANI_PINPIC_API_KEY DEVANI_ALTTEXT_API_KEY
DEVANI_SENDL_API_KEY DEVANI_SIVVY_API_KEY
DEVANI_MAILJET_API_KEY DEVANI_MAILJET_API_SECRET
DEVANI_MAIL_FROM_EMAIL DEVANI_MAIL_FROM_NAME
On a plain self-hosted install these are typically all unset — and that's fine; settings come from layer 2.
Layer 2: the settings store
Per-site values live in the JsonStore settings table, seeded by defaultSettings() and edited in the admin (or via MCP for allowlisted keys). This is where the setup wizard writes the keys it provisions.
Resolution order (the part that bites)
For integration credentials the canonical resolvers (defined in config.php so every layer shares them) prefer the environment first, then the setting. Notably, for the Tonta upload endpoint the env always wins even when a setting exists — the stored setting is just a copy of the default on old installs, and env is how managed/agency hosts point sites at their own uploader. Rule of thumb: env = operator intent, setting = site owner intent; operator wins for infrastructure, owner wins for content.
The managed-host signal
One presence check distinguishes install types: a DEVANI_TONTA_API_KEY in the environment means "managed host." Managed installs skip service signup in the setup wizard (services are pre-provisioned), and the Settings UI shows managed keys as "managed by your provider" — suppressing even the raw setting-key name, since it would leak the upstream vendor under whitelabel branding. Self-hosted installs (no env key) behave exactly as documented everywhere else — the gating is designed to be invisible to them.
Adding a config value
- Env read in
config.php(DEVANI_*naming). - Default row in
JsonStore::defaultSettings()with a human description — this is what makes it appear in the Settings UI, including on existing installs after update. - If secret: add to the write-only secrets list in
template.php, the managed-env map (for whitelabel suppression), and MCP'sallowedIntegrationSettings.