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.

ScopeSolutionsAppCatalogVersionInstalledVersion
Tenant10populated on all 10empty on all 10
Site1populatedpopulated

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

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, CanUpgrade

Find the tenant catalog's address rather than guessing it:

PowerShell
Get-PnPTenantAppCatalogUrl

Example output

Text
# -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               False

Explanation

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:

PowerShell
$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 solutions

Zero 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 Site fails 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-PnPTenantAppCatalogUrl instead of assuming /sites/appcatalog.
  • Both reads succeeded with the Sites.Read.All application role and nothing wider. See What application permission does PnP PowerShell need to read a SharePoint site?.

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

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