The core rule: default to zero-setup
If a feature needs the self-hoster to set something up first, the default is make it work with zero setup — not “document the env var.”An env var isn’t a feature flag for the user. It’s a tax on every install, and an undiscoverable failure: they enable the feature, it fails, nothing explains why. Order of preference for any required secret/key/config:
- Auto-provision it on first boot or first use. Reach for this first — it’s almost always possible.
- Derive it from something the instance already has.
- Gate visibly — if the prerequisite is genuinely absent, the feature must be disabled with an explanation, never enabled-but-broken.
- Require manual setup — last resort only, with a clear in-product error and a changelog note.
Failure modes we keep hitting
1. “Requires manual setup” shipped as enabled. A feature needs a per-instance key. Cloud has it, so it works; self-hosters don’t, so it fails — but the UI shows it as available.- S3 IAM Role / OIDC (PR #13439) depends on
AP_OIDC_RSA_PRIVATE_KEY. Unset, every connection fails:/api/v1/worker/oidc-token→400,/.well-known/jwks.json→400 SYSTEM_PROP_INVALID. The discovery doc returns200, so it looks deployed. The fix is to auto-generate the key, not document the env var. - Pipefy shipped the same shape — enabled in the UI, broken until an undocumented setup step is done.
cloud.activepieces.com, api.activepieces.com) works for us and dies for them. Degrade gracefully when a dependency is absent, and derive URLs from the instance’s own config — never hardcode Cloud’s.
4. Enabling a feature without a self-hosted path at all. A capability is built on a Cloud-only service (a managed secret store, a proprietary backend, a paid third-party with our key) and exposed everywhere anyway. Self-hosters can’t supply the missing half, so it can never work for them. Decide up front whether it has a self-hosted story — if not, gate it by edition rather than letting it surface broken.
If it genuinely can’t be zero-setup
The UI must never present the feature as working when it isn’t.- Disable it visibly with a message naming the missing prerequisite. Use existing patterns (
LockedFeatureGuard,enabled:on queries,platformMustHaveFeatureEnabled). - Fail with a real error that names the prerequisite and links the fix — not an opaque
400. - “Disable on Cloud” is a stopgap, not the goal. We want it to work everywhere, not be hidden on the one env where it happens to be set up.