Govern Teams App Permission and Setup Policies with Graph
Control which Teams apps users can install and what gets pinned, at scale, through Graph. A practical guide to app permission and setup policies for DevOps.
- #microsoft-teams
- #microsoft-graph
- #governance
- #policy
- #security
When our org grew past a few hundred Teams users, app sprawl became a real problem. People were side-loading random third-party apps, our internal ops bot was not pinned anywhere so nobody used it, and security had no consistent answer to “which apps are even allowed?” The fix lives in two policy types that most people configure by hand in the admin center but can — and at scale should — be managed through Microsoft Graph: app permission policies and app setup policies.
This guide explains both, shows the Graph calls to manage them, and covers assigning policies to users programmatically. I prototyped the policy-assignment scripts with an AI assistant because the Graph request shapes are tedious to remember. But policy changes are high-blast-radius: a wrong allow-list can block your entire org from an app, or worse, allow apps you meant to ban. Treat AI as a fast junior engineer — it drafts the request, a human reviews the policy intent against the actual app IDs, and you test on a pilot group before org-wide rollout. Never hand the model real tenant policy exports or admin credentials.
Two policies, two jobs
App permission policies control which apps a user is allowed to install or use — Microsoft apps, third-party apps, and custom (line-of-business) apps, each governed separately. You can allow all, block all, or allow/block specific apps.
App setup policies control what is pinned and pre-installed for a user — the apps that show in their Teams rail by default. This is how you make sure your ops bot is one tap away instead of buried.
The two work together: permission policy decides if an app is allowed at all; setup policy decides if it is surfaced prominently.
Reading the current policies
Both policy types live under the Teams admin configuration in Graph, exposed via the teamwork and appCatalogs surfaces (some operations are in beta, so pin your client to the right version and confirm before relying on them in production). To inspect installed custom apps in the catalog:
GET https://graph.microsoft.com/v1.0/appCatalogs/teamsApps?$filter=distributionMethod eq 'organization'
That returns your org’s line-of-business apps with their id values — the IDs you will reference when building an allow-list. Capture these first; a policy that references the wrong app ID is worse than no policy.
Defining an app setup policy to pin the ops bot
A setup policy carries an installedApps list (pre-installed) and a pinnedApps list (shown in the rail, in order). To make your internal incident bot pinned for a team, the per-team install call looks like this:
POST /teams/{team-id}/installedApps
{
"teamsApp@odata.bind":
"https://graph.microsoft.com/v1.0/appCatalogs/teamsApps/{ops-bot-app-id}"
}
For org-wide setup policy management — the pinned-app ordering that applies to assigned users — you configure the policy in the Teams admin surface and assign it. The pinned order matters: the apps highest in the list appear first in the user’s rail. Put the ops bot near the top or people will not find it.
Pro Tip: There is a global (org-wide default) setup policy and named custom policies. Test changes on a custom policy assigned to a pilot group first. Editing the global default policy applies to everyone immediately, and “everyone in the org lost their pinned apps” is a Friday-afternoon incident you do not want to author.
Assigning a policy to users programmatically
The reason to script this at all is bulk assignment — assigning a custom policy to a few hundred users through the admin UI is soul-crushing. Graph lets you assign policies to users, and you drive it from a directory query so the right cohorts get the right policy.
async function assignSetupPolicy(userId, policyId, graph) {
// Assign a named app setup policy to a single user.
await graph
.api(`/users/${userId}/teamwork/...`) // exact path: confirm against current docs
.post({ policyId });
}
async function assignToGroup(groupMembers, policyId, graph) {
for (const m of groupMembers) {
try {
await assignSetupPolicy(m.id, policyId, graph);
} catch (e) {
console.error(`Failed for ${m.id}: ${e.message}`);
// Continue; collect failures, do not abort the whole batch.
}
}
}
Note the per-user try/catch. One failed assignment should not abort the batch — collect failures, report them, and retry that subset. Some policy-assignment operations are batch-friendly via Graph’s $batch endpoint, which is far gentler on throttling for hundreds of users; reach for it once you have validated the single-user path.
Auditing what is actually allowed
Governance is not “set it and forget it.” Periodically enumerate which apps each policy allows and diff it against your approved list. If a third-party app shows up allowed that nobody signed off on, that is a finding. I run this audit on a schedule and route anomalies through the same review flow my code-review dashboard uses for other config drift, so a quietly-widened allow-list gets a human’s eyes. The “is this app supposed to be here” judgment is one I let AI flag but never auto-remediate.
Where AI helps and where it must not
The Graph request shapes for policy management are exactly the kind of tedious boilerplate I let Claude or GitHub Copilot draft. I refine the scripts in the prompt workspace, and the structured starters in my prompt packs cover the common assignment patterns. But the policy intent — which app IDs to allow, who gets which policy — is a human decision reviewed against the real catalog, on a pilot group, before any org-wide apply. The model never sees real tenant policy exports; I describe the intent in the abstract and review the generated calls against the actual IDs myself.
Conclusion
App permission policies decide what is allowed; app setup policies decide what is pinned. Managing both through Graph lets you govern app sprawl, pin your ops bot where people will use it, and assign policies in bulk instead of click-by-click. Pilot every change, keep an audit loop, and let AI draft the tedious Graph calls while a human owns the policy intent and keeps tenant data out of prompts. More governance patterns are in the Microsoft Teams category.
Download the Free 500-Prompt DevOps AI Toolkit
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.