30-second answer
Because installation happens per site, and the tenant catalog is not a
site. Get-PnPApp -Scope Tenant reports what the catalog holds, so
AppCatalogVersion is populated and InstalledVersion is empty on every
solution. -Scope Site reports what one site has, and there both are
populated.
What this proves
Read against a live tenant on 18 August 2026, with PnP.PowerShell 3.3.0.
| Scope | Solutions | AppCatalogVersion | InstalledVersion |
|---|---|---|---|
Tenant | 10 | populated on all 10 | empty on all 10 |
Site | 1 | populated | populated |
The tenant catalog in that read held ten client-side solutions, every one with a catalog version and not one with an installed version. The site collection catalog held one, and it carried both numbers, equal.
The names and versions shown below are placeholders. What was observed is the shape: which column is filled at which scope.
CanUpgrade was False on every solution in both scopes, which is consistent:
with nothing installed at tenant scope there is nothing to upgrade.
What it does not prove
It does not prove the tenant catalog is empty or unused. Ten solutions are there, deployed and available; what is absent is a per-site fact, in a place that has no site.
It does not establish behaviour on any other version. This is
PnP.PowerShell 3.3.0 against SharePoint Online on one date, and the shape of
AppMetadata has changed between versions before.
And it says nothing about tenant-wide deployment. Whether a solution is deployed to every site lives in the Tenant Wide Extensions list, not on the app object, so no property returned here answers it.
PowerShell
Connect-PnPOnline -Url https://<tenant>.sharepoint.com -Interactive -ClientId $appId
# What the tenant catalog holds
Get-PnPApp -Scope Tenant |
Select-Object Title, AppCatalogVersion, InstalledVersion, CanUpgrade
# What one site has. The catalog must exist on that site collection.
Connect-PnPOnline -Url https://<tenant>.sharepoint.com/sites/<site> -Interactive -ClientId $appId
Get-PnPApp -Scope Site |
Select-Object Title, AppCatalogVersion, InstalledVersion, CanUpgradeFind the tenant catalog's address rather than guessing it:
Get-PnPTenantAppCatalogUrlExample output
# -Scope Tenant
Title AppCatalogVersion InstalledVersion CanUpgrade
----- ----------------- ---------------- ----------
example-one-client-side-solution 1.5.2.0 False
example-two-client-side-solution 1.0.1.0 False
# -Scope Site
Title AppCatalogVersion InstalledVersion CanUpgrade
----- ----------------- ---------------- ----------
example-two-client-side-solution 1.0.0.0 1.0.0.0 FalseExplanation
The two scopes answer two different questions and the property set is shared between them, so the same object is returned with a field that only one of them can fill.
That matters the moment anything compares the two numbers. "Is this solution behind the version the catalog holds" is a per-site question: it needs the version a site is running and the version the catalog offers. At tenant scope only the second exists, so the comparison has no left-hand side.
The failure mode is quiet, and it is worth naming. A script that filters for solutions where the two versions differ will match nothing at tenant scope, because an empty string differs from nothing it is compared against only if the filter allows an empty operand. Most filters exclude it. The result is a count of zero that reads as "everything is current", produced from a set where nothing was comparable at all.
Count what you could compare, and say so:
$apps = @(Get-PnPApp -Scope Tenant)
$comparable = @($apps | Where-Object { $_.AppCatalogVersion -and $_.InstalledVersion })
$behind = @($comparable | Where-Object { $_.AppCatalogVersion -ne $_.InstalledVersion })
"$($behind.Count) behind, of $($comparable.Count) comparable, in $($apps.Count) solutions"
# 0 behind, of 0 comparable, in 10 solutionsZero out of zero is not a clean bill of health. To answer the question, collect the sites that host the solutions and compare there.
Production considerations
Get-PnPApp -Scope Sitefails with a resource-not-found error on a site with no site collection app catalog. That is the absence of a catalog, not an empty one, and the two deserve different handling.- The tenant catalog is not at a predictable address. Ask for it with
Get-PnPTenantAppCatalogUrlinstead of assuming/sites/appcatalog. - Both reads succeeded with the
Sites.Read.Allapplication role and nothing wider. See What application permission does PnP PowerShell need to read a SharePoint site?.
Related governance rules
None directly. What this establishes is where a version number exists and where it does not, which is a precondition for asking whether anything is behind, not a claim about a tenant.
References
- Get-PnPApp (PnP PowerShell)
- Use the App Catalog to make custom business apps available (Microsoft, checked 18 August 2026)
- Site collection app catalog (Microsoft, checked 18 August 2026)
Found something wrong? Suggest a correction. The article source is not public; the engine it cites is.
What this answer underwrites
The engine reads these Microsoft operations to collect evidence, so what is established here is what those collectors rest on.
spfxa site's app catalog: which solutions lag their version