Creating application users in Dataverse
Every non-interactive workload that talks to Dataverse needs an identity. You could hand it a real user's credentials and inherit their license cost, their MFA prompts, their password rotation schedule, and an audit trail that lies about who actually changed the data. Or you could create an application user: a non-interactive identity that costs nothing, requires no license, and gets its own independent API budget.
Dataverse supports two backing identity types for application users: Entra app registrations and user-assigned managed identities. App registrations are the classic approach -- they work from anywhere (on-premises, other clouds, CI runners), but you own a client secret that expires and can leak. Managed identities eliminate the secret entirely: Azure handles token issuance, there's nothing to rotate, nothing to store, nothing to leak. The trade-off is that your workload must run on Azure compute.
If you arrived here from the rotating application users post looking for how to actually create those users -- this is that post. We'll walk through both types manually via Power Platform Admin Center, then script the whole thing with az and pac CLI for when you need more than one or two.
When to use which
The choice is a deployment-topology decision, not a preference:
| App Registration | Managed Identity | |
|---|---|---|
| Secret management | Client secret or certificate -- you rotate it | None -- Azure handles tokens |
| Works outside Azure | Yes | No |
| Setup complexity | App registration + service principal + secret | One ARM resource |
| Best fit | CI/CD runners, on-prem servers, multi-cloud | Azure Functions, App Service, Container Apps, AKS |
If your workload runs on Azure compute that supports managed identity assignment, use a managed identity. No secret to store means no secret to leak, no expiry calendar to manage, no incident when someone commits a credential to source control. If it runs outside Azure -- a GitHub Actions runner, an on-premises server, another cloud -- an app registration is your only option.
Both identity types register the same way in Dataverse: as an application user with a security role. The Power Platform Admin Center flow and the pac admin assign-user command are identical for both.
Creating an app registration-backed user
Register the app in Entra ID
Open the Azure portal, navigate to Microsoft Entra ID > App registrations, and click New registration.
Give it a descriptive name that identifies the workload, not a generic "API User". Set Supported account types to Accounts in this organizational directory only (single tenant). You don't need a redirect URI. Click Register.
Copy the Application (client) ID from the overview page -- you'll need it for the PPAC step.
Next, create a client secret. Go to Certificates & secrets > Client secrets > New client secret. Give it a description and an expiry. Click Add and copy the secret value immediately -- you won't see it again after you leave this page.
Register as application user in PPAC
Open Power Platform Admin Center, navigate to your environment, then Settings > Users + permissions > Application users.
Click New app user > Add an app. The picker shows all app registrations in your tenant. Find yours by name and select it.
Choose the Business unit (root is fine for most cases) and assign a Security role.
Click Create. The application user appears in the list, ready to authenticate.
Creating a UAMI-backed user
Create the managed identity
Open the Azure portal and search for Managed Identities. Click Create.
Pick a subscription, resource group, and region. Give it a name that identifies the workload -- something like uami-dataverse-sync-prod. Click Review + create > Create.
Copy the Client ID from the overview page. PPAC uses this to find the identity.
Register as application user in PPAC
The PPAC flow is the same as for app registrations, with one difference: managed identities don't show up in the app picker by name. You search by Client ID instead.
Navigate to your environment > Settings > Users + permissions > Application users > New app user > Add an app. Paste the Client ID into the search box.
Select the identity, assign a business unit and security role, and click Create.
Scripting with az and pac CLI
The manual flow is fine for a handful of users. When you need a pool -- for instance, to rotate them under API throttling -- scripting saves the clicking. Both scripts below use az for Entra and ARM operations and pac for the Dataverse registration.
App registration
The script creates an Entra app registration, a service principal, a client secret, and registers the result as an application user in Dataverse -- the same four steps from the manual walkthrough, end to end.
#!/bin/bash
APP_NAME="Dataverse API User 01"
ENV_URL="https://yourorg.crm4.dynamics.com"
ROLE_NAME="Integration User"
# create entra app registration
APP_ID=$(az ad app create \
--display-name "$APP_NAME" \
--sign-in-audience AzureADMyOrg \
--query appId -o tsv)
# create service principal
az ad sp create --id "$APP_ID" --output none
# generate client secret (1 year)
CREDS=$(az ad app credential reset \
--id "$APP_ID" \
--display-name "dataverse" \
--years 1)
SECRET=$(echo "$CREDS" | jq -r '.password')
TENANT=$(echo "$CREDS" | jq -r '.tenant')
echo "Client ID: $APP_ID"
echo "Secret: $SECRET"
echo "Tenant: $TENANT"
# register as application user in Dataverse
pac admin assign-user \
--environment "$ENV_URL" \
--user "$APP_ID" \
--role "$ROLE_NAME" \
--application-user$AppName = "Dataverse API User 01"
$EnvUrl = "https://yourorg.crm4.dynamics.com"
$RoleName = "Integration User"
# create entra app registration
$AppId = az ad app create `
--display-name $AppName `
--sign-in-audience AzureADMyOrg `
--query appId -o tsv
# create service principal
az ad sp create --id $AppId --output none
# generate client secret (1 year)
$Creds = az ad app credential reset `
--id $AppId `
--display-name "dataverse" `
--years 1 | ConvertFrom-Json
$Secret = $Creds.password
$Tenant = $Creds.tenant
Write-Host "Client ID: $AppId"
Write-Host "Secret: $Secret"
Write-Host "Tenant: $Tenant"
# register as application user in Dataverse
pac admin assign-user `
--environment $EnvUrl `
--user $AppId `
--role $RoleName `
--application-userManaged identity
Same idea, fewer moving parts. The script creates a user-assigned managed identity and registers it in Dataverse. The one wrinkle: a freshly created UAMI needs a moment to propagate through Entra before pac can find it, so the script retries the registration in a loop.
#!/bin/bash
IDENTITY_NAME="uami-dataverse-api-001"
RESOURCE_GROUP="rg-integrations"
LOCATION="westeurope"
ENV_URL="https://yourorg.crm4.dynamics.com"
ROLE_NAME="Integration User"
# create user-assigned managed identity
UAMI=$(az identity create \
--name "$IDENTITY_NAME" \
--resource-group "$RESOURCE_GROUP" \
--location "$LOCATION")
CLIENT_ID=$(echo "$UAMI" | jq -r '.clientId')
echo "Managed Identity: $IDENTITY_NAME"
echo "Client ID: $CLIENT_ID"
# register as application user in Dataverse (retry until Entra propagation completes)
registered=0
for attempt in $(seq 1 12); do
if pac admin assign-user \
--environment "$ENV_URL" \
--user "$CLIENT_ID" \
--role "$ROLE_NAME" \
--application-user 2>/dev/null; then
registered=1
break
fi
echo " attempt $attempt/12 - not yet available, retrying in 10s..."
sleep 10
done
if [ "$registered" -ne 1 ]; then
echo "Failed to create app user after 2 minutes" >&2
exit 1
fi$IdentityName = "uami-dataverse-api-001"
$ResourceGroup = "rg-integrations"
$Location = "westeurope"
$EnvUrl = "https://yourorg.crm4.dynamics.com"
$RoleName = "Integration User"
# create user-assigned managed identity
$Uami = az identity create `
--name $IdentityName `
--resource-group $ResourceGroup `
--location $Location | ConvertFrom-Json
$ClientId = $Uami.clientId
Write-Host "Managed Identity: $IdentityName"
Write-Host "Client ID: $ClientId"
# register as application user in Dataverse (retry until Entra propagation completes)
for ($attempt = 1; $attempt -le 12; $attempt++) {
$output = pac admin assign-user `
--environment $EnvUrl `
--user $ClientId `
--role $RoleName `
--application-user 2>&1
if ($LASTEXITCODE -eq 0) { Write-Host $output; break }
Write-Host " attempt $attempt/12 - not yet available, retrying in 10s..."
Start-Sleep -Seconds 10
}
if ($LASTEXITCODE -ne 0) { Write-Error "Failed to create app user after 2 minutes"; exit 1 }Wrap-up
Two identity types, same result: a non-interactive Dataverse user with its own API budget and no license cost. App registrations work everywhere but saddle you with a secret to rotate. Managed identities eliminate the secret entirely when your workload runs on Azure. Either way, the scripts above get you from zero to a registered application user in under a minute -- and when you need a pool of them, you're one for loop away from the user rotation pattern that turns a per-user API limit into a multiplier.
Share this article
About the Author
Georg is a senior solution architect specializing in .NET, Azure, and Dynamics 365. He helps organizations design and build scalable, maintainable enterprise systems. When he's not writing code, he's writing about it here.
Learn more about Georg