To find empty App Service plans in Azure, query Azure Resource Graph for plans whose numberOfSites, the number of apps assigned to the plan, is 0. A plan on a dedicated tier, Basic or above, is charged for each of its instances whether or not any app runs on it, so an empty one costs money and serves nothing.
| Method | Best fit |
|---|---|
| PowerShell | A CSV of empty plans across your enabled subscriptions, with each plan's SKU and tier. |
| Admin centre | A quick query in Resource Graph Explorer, with nothing to install. |
| Altivane | Recommended for monthly cost reviews, with empty plans beside the other unused resources in 1 workbook. |
Before you start
Use an account with read access to the subscriptions, such as the Reader role: Resource Graph returns nothing for resources you can't read. Microsoft lists no licence for Resource Graph, and it indexes changes with a short delay, so an app moved off a plan a moment ago can still be counted.
Know what an empty plan means. On the dedicated tiers each instance of the plan is charged, so the cost follows the plan's size and instance count, not its apps. Empty plans build up during development and when apps move between plans, but one can also be kept on purpose, so check with its owner before you remove it. The examples read and export; they don't delete or scale any plan. The script's finally block disconnects the account and clears its saved Azure contexts.
Option 1
Find empty App Service plans with PowerShell
Install the modules once. The script queries Resource Graph across the enabled subscriptions, follows every page of results, and saves each plan with no apps.
Install-Module -Name Az.Accounts, Az.ResourceGraph -Repository PSGallery -Scope CurrentUser$ErrorActionPreference = 'Stop'
Connect-AzAccount -ErrorAction Stop
try {
$subscriptionIds = @(
Get-AzSubscription | Where-Object State -eq 'Enabled' | Select-Object -ExpandProperty Id
)
if ($subscriptionIds.Count -eq 0) {
Write-Output 'No enabled subscriptions are available to this account.'
return
}
$query = @(
'Resources'
'| where type =~ "microsoft.web/serverfarms"'
'| where toint(properties.numberOfSites) == 0'
'| project id, name, resourceGroup, subscriptionId, location,'
'skuName = tostring(sku.name), skuTier = tostring(sku.tier)'
'| order by id asc'
) -join ' '
$rows = [System.Collections.Generic.List[object]]::new()
$skipToken = $null
do {
$arguments = @{ Query = $query; Subscription = $subscriptionIds; First = 1000 }
if ($skipToken) { $arguments.SkipToken = $skipToken }
$page = Search-AzGraph @arguments
foreach ($record in $page.Data) { $rows.Add($record) }
$skipToken = $page.SkipToken
} while ($skipToken)
if ($rows.Count -eq 0) {
Write-Output 'Every App Service plan in the enabled subscriptions has at least 1 app.'
} else {
$rows | Sort-Object skuTier, name |
Select-Object name, skuName, skuTier, resourceGroup, subscriptionId, location, id |
Export-Csv -Path './empty-app-service-plans.csv' -NoTypeInformation -Encoding utf8
Write-Output "$($rows.Count) empty plans saved to empty-app-service-plans.csv."
}
} finally {
Disconnect-AzAccount | Out-Null
}The CSV holds each empty plan's name, SKU, tier, resource group, subscription, location and resource ID. To leave out Free plans, which carry no charge, add '| where sku. before the project line, as Microsoft's own cost query does. Resource Graph paging needs id in the results, so keep it if you edit the query.
Option 2
Query empty plans in Resource Graph Explorer
- Sign in to the Azure portal with an account that can read the subscriptions.
- Search for Resource Graph Explorer and open it.
- Paste this query in Query 1 and choose Run query:
Resources | where type =~ 'microsoft.web/serverfarms' | where toint(properties.numberOfSites) == 0 | project name, skuName = tostring(sku.name), skuTier = tostring(sku.tier), resourceGroup, subscriptionId, location - Read the plans in Results, or choose Download as CSV for a file.
Explorer suits an occasional check with nothing to install. Open a plan from the results to see it in the portal before you decide anything. For the same list each month beside the other unused resources, use Altivane.
Option 3 ยท Recommended for monthly cost reviews
Find empty App Service plans with Altivane
- Open Altivane and choose the tenant.
- Run the Azure Unused Resources report.
- Open the Excel workbook and find Empty App Service plans: the plans Azure says hold 0 apps, with their SKU, resource group, subscription and location.
- Read SKU, which names the tier, to tell paid plans from Free ones, which are listed too.
A plan Azure gives no app count for is left out rather than guessed at. The same workbook lists unattached disks, unused public IP addresses, unattached network interfaces and empty resource groups, and the Azure Resources Not in Use scorecard report counts empty App Service plans in its summary beside them. The report needs a tenant signed in with a certificate and the Reader role for Altivane on the subscriptions.
Contact usCommon questions
Does an empty Free plan cost anything?
No. Microsoft names Free as the 1 tier with no charge for its compute, so an empty Free plan is clutter rather than cost. Leave Free plans out of the query if you only want savings.
Why does a plan with no apps still show CPU and memory use?
App Service runs its own system processes on a plan even when it holds no apps, for platform work such as security updates and monitoring. That use is normal and is no sign that an app is still there.
Where can I find other unused Azure resources?
The same Altivane report lists them, and each has a guide of its own. See how to find empty resource groups in Azure.
Microsoft references
Technical references checked on 7 November 2026. The example scripts have been reviewed and syntax checked; they have not been run against a live tenant.