Hard-coding URLs and secrets into flows and plugins is the most common ALM sin in Power Platform. Environment variables exist to fix it. The feature is mature; the discipline around using it is not.
Three types, three uses
- Plain (string, number, boolean, JSON): for non-sensitive config like API URLs, feature flags, retry counts.
- Data source: bind a connection reference to an environment-specific data source (e.g., a SharePoint site URL).
- Secret: backed by Azure Key Vault, the only safe way to handle keys.
The Key Vault binding
Secret-type environment variables require a Key Vault, a managed identity for the Power Platform service principal, and a Key Vault Secrets User role assignment. Once configured, the secret value lives in Key Vault and rotates without redeploying the solution.
Azure Key Vault -> Access control -> Add role assignment
-> Role: Key Vault Secrets User
-> Assignee: Dataverse service principal (per environment)
The variable in Dataverse stores the Key Vault URI, not the secret. Solution promotion ships the URI; the runtime resolves the secret per environment.
The schema-name discipline
Environment variables have schema names that travel with solutions. Once you publish new_apibaseurl to production, you cannot rename it without breaking every flow referencing it. Pick names you can live with for years.
Default values are environment-specific
A variable can have a Default Value (travels with the solution) and a Current Value (per-environment override). The pattern that works:
- Default Value = dev environment value (or empty).
- Current Value = set per environment after import.
Never put production secrets in Default Value. They land in your source-controlled solution.zip.
Reading from a flow
Action: Get Environment Variable
Variable Schema Name: new_stripeapikey
Output: dynamicValue('new_stripeapikey/Value')
Cache the result inside the flow run if you read it more than once; each read hits Dataverse.
Reading from a plugin
var query = new QueryExpression("environmentvariablevalue") {
ColumnSet = new ColumnSet("value"),
Criteria = new FilterExpression { Conditions = {
new ConditionExpression("environmentvariabledefinitionid.schemaname",
ConditionOperator.Equal, "new_apibaseurl")
}}
};
var result = service.RetrieveMultiple(query).Entities.FirstOrDefault();
For secrets, plugins must call RetrieveEnvironmentVariableSecretValue action which returns the resolved secret without exposing the URI.
What breaks ALM
The two anti-patterns that defeat the system:
- Setting environment variable values from inside the solution itself. The value travels and overrides target environments.
- Building the variable name dynamically (
concat('new_url_', envName)). The reference cannot be tracked by dependency analysis.
The audit angle
Changes to environment variable values are audited. Changes to the underlying Key Vault secret are audited in Azure. Both audit trails are needed for SOX-style evidence; surface them in your monitoring stack.
What to do this week
Grep your flows and plugins for hard-coded URLs and API keys. Each one is a migration to an environment variable. Start with anything resembling a credential.