Microsoft Teams governance

How to find inactive teams in Microsoft Teams

Find teams with no recent activity with Microsoft Graph PowerShell, export the Team usage report from the admin centre, or use Altivane's Teams report for a rated list of inactive teams.

By the Published

To find inactive teams in Microsoft Teams, download the Teams team activity report from Microsoft Graph, match it to your teams by ID, and keep those whose Last Activity Date is older than your limit or missing. The report hides team names and IDs if your tenant conceals names in usage reports, and its data runs up to 2 days behind.

Choose the method that fits the job
MethodBest fit
PowerShellA CSV of teams with no activity for a set number of days, longest idle first.
Admin centreReading each team's last activity date in Microsoft's usage report, or a one-off export.
AltivaneRecommended for regular reviews, with the days set once and the inactive share rated against a threshold.

Before you start

Use an account that can view usage reports, such as Reports Reader, and that can read teams. The PowerShell example requests Reports.Read.All and Team.ReadBasic.All; your organisation may require administrator consent. Microsoft says a delegated report read also needs the signed-in person to hold a suitable Microsoft Entra ID administrator role.

Last Activity Date is the latest day any member of the team did something in it. The activity counts come from the Teams clients: posts by apps and emails sent to a channel are not counted. Dates are in UTC. The examples read and export; they archive or delete no team.

Option 1

Find inactive teams with PowerShell

Install the modules once, then connect and export the teams idle for more than $days days. The script counts a team missing from the 180 day report as having no activity in that time.

PowerShell
# Install once, if the modules are not already installed.
Install-Module Microsoft.Graph.Authentication, Microsoft.Graph.Teams -Scope CurrentUser
PowerShell
$ErrorActionPreference = 'Stop'
$days = 90
$utc = [System.Globalization.DateTimeStyles]::AssumeUniversal -bor
    [System.Globalization.DateTimeStyles]::AdjustToUniversal
$today = (Get-Date).ToUniversalTime().Date
Connect-MgGraph -Scopes 'Reports.Read.All', 'Team.ReadBasic.All' -NoWelcome
try {
    $download = Join-Path $PWD 'team-activity-download.csv'
    $uri = "https://graph.microsoft.com/v1.0/reports/getTeamsTeamActivityDetail(period='D180')"
    Invoke-MgGraphRequest -Method GET -Uri $uri -OutputFilePath $download
    $activity = @{}
    foreach ($row in Import-Csv -Path $download) { $activity[$row.'Team Id'] = $row }
    Remove-Item -Path $download
    $teams = @(Get-MgTeam -All -Property 'id', 'displayName', 'visibility')
    $known = @($teams | Where-Object { $activity.ContainsKey($_.Id) }).Count
    if ($activity.Count -gt 0 -and $known -eq 0) {
        throw 'No team matches the report. Your tenant may conceal names in usage reports.'
    }
    $report = @(foreach ($team in $teams) {
        $row = $activity[$team.Id]
        $date = if ($row) { $row.'Last Activity Date' } else { '' }
        $idle = $null
        if ($date) {
            $last = [datetime]::ParseExact($date, 'yyyy-MM-dd',
                [System.Globalization.CultureInfo]::InvariantCulture, $utc).ToUniversalTime()
            $idle = [int]($today - $last.Date).TotalDays
        }
        if ($null -eq $idle -or $idle -gt $days) {
            [pscustomobject]@{
                Team = $team.DisplayName
                TeamId = $team.Id
                Visibility = $team.Visibility
                LastActivity = $date
                DaysIdle = $idle
            }
        }
    })
    if ($report.Count -eq 0) {
        Write-Output "No team has been idle for more than $days days."
    } else {
        $order = @{
            Expression = { if ($null -eq $_.DaysIdle) { [int]::MaxValue } else { $_.DaysIdle } }
            Descending = $true
        }
        $report | Sort-Object $order |
            Export-Csv -Path './inactive-teams.csv' -NoTypeInformation -Encoding utf8
        Write-Output "$($report.Count) teams are idle for more than $days days or have no date."
    }
} finally {
    Disconnect-MgGraph | Out-Null
}

The CSV has 1 row for each inactive team: those with no activity date first, then the longest idle. The script stops if no team matches the report, which is what a tenant that conceals names returns. Raise or lower $days to widen or narrow the list; keep it under 180, the longest period the report covers.

Option 2

Review team activity in the admin centre

  1. Open the Microsoft 365 admin centre for the correct tenant.
  2. Go to Reports > Usage. If Reports is missing, select Show all first.
  3. Under Reports, select Microsoft Teams, then the Team usage tab.
  4. Choose a period of 7, 30, 90 or 180 days, and select Choose columns to add Last activity date if it is not shown.
  5. Select Export to download the report as a CSV file, then sort it by last activity date in Excel. The oldest dates are the teams to review.

This suits a quick look or a one-off file, and it is the same data the script downloads. To be sure every team is covered, compare the export with the teams list under Teams > Manage teams in the Teams admin centre.

Common questions

Why are the team names hidden in the report?

Microsoft hides user, group and site names in usage reports by default. A Global Administrator can turn that off in the Microsoft 365 admin centre under Settings > Org settings, the Services tab, Reports, by clearing Conceal user, group, and site names in all reports. That changes the setting for the whole tenant, so check your privacy rules first.

Why does a busy channel look idle?

The report counts what people do in the Teams clients. Posts and replies made by apps, and emails sent to a channel, are not counted, so a team whose channels are fed only by an app or by email can show no activity even though messages arrive every day.

What about teams that have no owner?

An inactive team often has no owner left to keep or archive it. To find those, see how to find teams without owners in Microsoft Teams.

Microsoft references

Technical references checked on 6 November 2026. The example scripts have been reviewed and syntax checked; they have not been run against a live tenant.

Talk to us about Altivane

Questions, or pricing? Get in touch.

Contact us