Azure Built-in vs Custom Roles: When to Use Each

Azure ships with over 300 built-in roles covering everything from broad subscription control to narrow service-specific permissions. Before creating a custom role, it is worth understanding what built-in roles exist and when they genuinely fall short — because custom roles add maintenance overhead that is only justified when built-in options truly do not fit.

What built-in roles Azure provides

Built-in roles in Azure are divided into a few categories:

General-purpose roles

These apply across all resource types. You have already seen Owner, Contributor, and Reader in the Azure RBAC overview. There is also User Access Administrator, which can only manage access (assign roles) but cannot manage resources — useful for delegating access management without giving full Owner permissions.

Service-specific management-plane roles

These restrict Contributor-like permissions to a single service. For example:

  • Virtual Machine Contributor — manage VMs but not the virtual networks or storage accounts they connect to
  • Network Contributor — manage virtual networks, subnets, and NSGs but nothing else
  • SQL DB Contributor — manage SQL databases but not their security policies or storage
  • Website Contributor — manage App Service web apps but not their DNS

Service-specific data-plane roles

These control access to data stored inside a service, not the service configuration itself:

  • Storage Blob Data Reader — read blobs from storage containers
  • Storage Blob Data Contributor — read, write, and delete blobs
  • Key Vault Secrets User — read secret values from Key Vault
  • Key Vault Secrets Officer — create and manage secrets in Key Vault (but not keys or certificates)

You can see all available built-in roles with the CLI:

az role definition list \
  --custom-role-only false \
  --output table \
  --query "[].{Name:roleName, Description:description}" | head -50

When built-in roles are enough

For most teams, built-in roles cover their needs. Before creating a custom role, check whether a service-specific built-in role fits. The pattern to follow is:

  1. Identify the service your user or application needs to interact with
  2. Search for a built-in role scoped to that service
  3. Check whether that role grants more than necessary (e.g., does it allow deletes when you only need reads?)
  4. If the permissions are close but not exact, consider whether the extra permissions pose a realistic risk

Often, accepting a slightly broader built-in role is preferable to maintaining a custom role. The question is whether the extra permissions represent a genuine security risk in your environment.

When you genuinely need a custom role

There are three scenarios where custom roles are the right choice:

1. You need a subset of a built-in role’s permissions

The most common case. You want a role that can start and stop VMs but cannot create or delete them. Virtual Machine Contributor includes create and delete. No built-in role does exactly start/stop-only. This is where a custom role earns its place.

2. You need to combine permissions from multiple services

You want a monitoring agent that can read VM metrics, read storage diagnostics data, and write to a Log Analytics workspace — but cannot modify any of those resources. No single built-in role covers exactly this combination.

3. You need a role for an internal automation pattern

You have an internal platform that provisions resources on behalf of developers. It needs specific create permissions on certain resource types but should be explicitly blocked from touching production environments. Custom roles with carefully chosen assignable scopes can enforce this cleanly.

Custom role JSON structure

A custom role is defined as a JSON document. Here is the structure with every field explained before we look at a real example:

{
  "Name": "Human-readable name for the role",
  "Id": null,
  "IsCustom": true,
  "Description": "Description of what this role does",
  "Actions": [
    "Microsoft.Provider/resourceType/action"
  ],
  "NotActions": [
    "Microsoft.Provider/resourceType/actionToExclude"
  ],
  "DataActions": [
    "Microsoft.Provider/resourceType/dataAction"
  ],
  "NotDataActions": [],
  "AssignableScopes": [
    "/subscriptions/YOUR_SUBSCRIPTION_ID"
  ]
}
  • Name — the display name. Choose something descriptive and specific, like “VM Operator” rather than “Custom Role 1”.
  • Id — leave as null when creating. Azure generates the GUID for you.
  • IsCustom — always true for custom roles.
  • Actions — management-plane operations this role allows. These are strings in the format Microsoft.Provider/resourceType/action. A wildcard * can be used to allow all actions under a path.
  • NotActions — management-plane operations to exclude from Actions. If an operation appears in both Actions and NotActions, NotActions wins and the action is denied.
  • DataActions — data-plane operations this role allows. Separate from Actions.
  • NotDataActions — data-plane operations to exclude from DataActions.
  • AssignableScopes — the subscriptions (or management groups, or resource groups) where this role can be assigned. You must specify at least one scope. Custom roles are only visible and assignable within these scopes.

Real example: a VM Operator custom role

Here is a realistic custom role for a VM operations team. They need to start, stop, restart, and deallocate VMs — and they need to read VM status and configuration to do their jobs. They should not be able to create new VMs, delete existing ones, or modify network or storage resources.

{
  "Name": "VM Operator",
  "Id": null,
  "IsCustom": true,
  "Description": "Can start, stop, restart, and deallocate virtual machines. Cannot create or delete VMs or modify network or storage resources.",
  "Actions": [
    "Microsoft.Compute/virtualMachines/start/action",
    "Microsoft.Compute/virtualMachines/powerOff/action",
    "Microsoft.Compute/virtualMachines/restart/action",
    "Microsoft.Compute/virtualMachines/deallocate/action",
    "Microsoft.Compute/virtualMachines/read",
    "Microsoft.Compute/virtualMachines/instanceView/read",
    "Microsoft.Compute/virtualMachineScaleSets/read",
    "Microsoft.Compute/virtualMachineScaleSets/virtualMachines/read",
    "Microsoft.Resources/subscriptions/resourceGroups/read"
  ],
  "NotActions": [],
  "DataActions": [],
  "NotDataActions": [],
  "AssignableScopes": [
    "/subscriptions/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
  ]
}

What each action does

Let’s walk through each permission in this definition:

  • Microsoft.Compute/virtualMachines/start/action — start a stopped or deallocated VM. This is an action operation (note the /action suffix), meaning it triggers a state change rather than reading or writing configuration.
  • Microsoft.Compute/virtualMachines/powerOff/action — stop a VM but keep it allocated (you still pay for the compute). Different from deallocate.
  • Microsoft.Compute/virtualMachines/restart/action — restart a running VM.
  • Microsoft.Compute/virtualMachines/deallocate/action — stop the VM and release the underlying compute resources. You stop paying for compute after deallocation.
  • Microsoft.Compute/virtualMachines/read — read VM configuration. Without this, the operators cannot even see the VMs they are supposed to manage.
  • Microsoft.Compute/virtualMachines/instanceView/read — read runtime state (running, stopped, etc.). Required for monitoring and alerting.
  • Microsoft.Compute/virtualMachineScaleSets/read and …/virtualMachines/read — read access for scale sets, since many production workloads use VMSS rather than individual VMs.
  • Microsoft.Resources/subscriptions/resourceGroups/read — read resource group metadata. Without this, the portal cannot display resource groups, and CLI commands that list resources by resource group fail.

Notice what is absent: there is no Microsoft.Compute/virtualMachines/write, no Microsoft.Compute/virtualMachines/delete, and nothing from Microsoft.Network or Microsoft.Storage. The VM operators cannot accidentally (or maliciously) provision new machines, delete existing ones, or modify network security groups.

Creating the custom role from this JSON

Save the JSON above as vm-operator-role.json, then create the role:

az role definition create --role-definition vm-operator-role.json

After creation, assign it like any other role:

az role assignment create \
  --assignee "ops-team-group-object-id" \
  --role "VM Operator" \
  --resource-group "production-compute"

Updating a custom role

When you need to add or remove permissions, update the JSON and run:

az role definition update --role-definition vm-operator-role.json

For updates, the JSON must include the role’s Id (the GUID Azure assigned when you created it). You can get this with:

az role definition list \
  --custom-role-only true \
  --query "[?roleName=='VM Operator'].{Id:id, Name:roleName}" \
  --output table

Limitations of custom roles

Custom roles have real costs beyond the definition work. Before creating one, understand these constraints:

The 5,000-role limit

Each Microsoft Entra ID tenant can have at most 5,000 custom role definitions. In practice, most organizations hit organizational complexity problems long before this technical limit, but it is worth knowing.

Maintenance burden

Built-in roles are maintained by Microsoft. When Azure adds new features to a service, Microsoft updates the relevant built-in roles. Your custom roles are not automatically updated. If you use a wildcard like Microsoft.Compute/virtualMachines/*, new actions are automatically included — but if you list specific actions, new operations require explicit updates to your role definition.

Assignable scope restrictions

Custom roles are only visible in the AssignableScopes you define. If your organization spans multiple subscriptions and you need the role in all of them, you must list all subscriptions in AssignableScopes, or use a management group scope. This is easy to forget when adding new subscriptions later.

Warning

Avoid creating custom roles with */read at broad scopes as a convenience. This gives read access to all resource types across the scope, including sensitive configuration data for resources like Key Vault, SQL Server, and networking. Use service-specific read roles instead.

Finding the right permission strings

When building a custom role, you need to know the exact operation strings. Two tools help with this:

List operations for a resource provider

az provider operation show \
  --namespace "Microsoft.Compute" \
  --query "resourceTypes[?name=='virtualMachines'].operations[].{Name:name, Desc:description}" \
  --output table

Export an existing role as a starting point

If you want to start from a built-in role and trim it down, export its definition first:

az role definition list \
  --name "Virtual Machine Contributor" \
  --output json

This gives you the full JSON with all Actions. Remove the ones you do not want, set IsCustom to true, change the name, and you have a starting point. This is often faster than building from scratch.

Following the principle of least privilege when building custom roles means starting with the minimum set of actions needed and only adding more when a specific use case requires it.

Common mistakes with custom roles

  1. Using wildcards too broadly. Microsoft.Compute/* includes create and delete operations you probably did not intend to include. Be specific with action names unless you genuinely want to grant all operations for a resource type.
  2. Forgetting to include read permissions. Many action operations (like starting a VM) implicitly require that the principal can also read the resource. If you only include action permissions without corresponding read permissions, operations may fail with confusing error messages.
  3. Not updating AssignableScopes when adding subscriptions. A custom role only appears in scopes listed in AssignableScopes. New subscriptions added to your tenant will not automatically include your custom roles.
  4. Creating custom roles when a built-in role would do. Always check whether a service-specific built-in role fits before creating a custom one. Check the RBAC overview for guidance on evaluating built-in roles.
  5. Not documenting the role’s purpose. The Description field exists for a reason. Teams that discover an undocumented custom role months later have no way to understand its intent without auditing every permission manually.

Frequently asked questions

How many built-in roles does Azure have?

Azure has over 300 built-in roles. Most are service-specific roles like Virtual Machine Contributor, Storage Blob Data Reader, or Key Vault Secrets User. The complete list is available in the Azure documentation and via the CLI with az role definition list --output table.

What are the limitations of custom roles in Azure?

Each Microsoft Entra ID tenant can have at most 5,000 custom role definitions (2,000 for Azure China and Government). Custom roles must be created and maintained by your team, unlike built-in roles which Microsoft keeps updated as services evolve. You must specify at least one assignable scope when creating a custom role.

Can I modify a built-in role in Azure?

No. Built-in roles are read-only and managed by Microsoft. If a built-in role does not fit your needs exactly, you must create a custom role. You can use a built-in role as the starting point for a custom role by exporting its definition and then modifying it.

Last verified: 19 March 2026 Cloud services change frequently. Verify details against official documentation before making infrastructure decisions.