A practical guide and reusable templates for displaying a Power Apps Code App as a Microsoft Teams personal tab.
Last reviewed: 7 September 2026 against current Microsoft Learn documentation and a field-tested Teams web implementation.
Important
This is a custom Teams tab that embeds the authenticated Power Apps player. It is not a native Add to Teams export for Code Apps. The combined approach has been field-tested in Teams web, but you must validate every client, identity, permission, and workload your organization intends to support.
Microsoft Teams
-> TeamsJS wrapper (stable HTTPS page)
-> authenticated Power Apps play URL (iframe)
-> your Code App
-> Dataverse / connectors / APIs using existing Power Platform permissions
The Teams package contains only a manifest and icons. It points Teams to a small wrapper page. The wrapper initializes TeamsJS and then loads the official Power Apps play URL in an iframe. Power Apps remains responsible for authentication, app sharing, licensing, connections, and data authorization.
Publishing or installing the Teams package does not grant:
- access to the Power Apps environment or Code App;
- a Power Apps license;
- Dataverse security roles;
- connector consent or data permissions;
- guest or cross-tenant access;
- automatic installation or pinning.
Teams availability, Power Apps access, and data access are three separate controls.
- A deployed and working Power Apps Code App.
- Power Platform environment administrator access for Code Apps CSP settings.
- A Teams tenant that permits custom apps for the pilot users.
- A Teams or Global Administrator for organization-catalog publication.
- A stable HTTPS location for the wrapper (recommended).
- Appropriate Power Apps licensing, app sharing, environment access, Dataverse roles, and connector permissions for every tester.
Record these values before starting:
| Placeholder | Meaning |
|---|---|
ENVIRONMENT_ID |
Power Platform environment ID |
CODE_APP_ID |
Code App ID from power.config.json or Power Apps |
TENANT_ID |
Microsoft Entra tenant ID |
TEAMS_APP_GUID |
New GUID for this Teams app; retain it for updates |
SHELL_URL |
Public HTTPS URL of teams-host.html |
SHELL_ORIGIN |
Origin only, for example https://example.com |
Copy these files into the existing Code App project:
templates/teams-host.html -> teams-host.html
templates/src/teams-host.ts -> src/teams-host.ts
templates/src/teams-host.css -> src/teams-host.css
Replace ENVIRONMENT_ID, CODE_APP_ID, and TENANT_ID in teams-host.ts.
Install TeamsJS through your organization's approved package registry:
npm install --save-exact @microsoft/teams-jsAdd teams-host.html as an additional Vite input. Keep the Code App's normal buildEntryPoint as index.html:
build: {
rollupOptions: {
input: {
app: 'index.html',
teamsHost: 'teams-host.html',
},
},
},Run the project's existing checks and build:
npm run lint
npm run buildDeploy the wrapper output and its generated assets to an approved, stable HTTPS host. The host must:
- allow Microsoft Teams to frame it;
- allow the wrapper to frame
https://apps.powerapps.com; - allow TeamsJS network requests required by the pinned SDK version;
- serve the wrapper and generated assets with correct MIME types;
- remain available for the lifetime of the Teams app release.
Use the resulting wrapper URL as SHELL_URL.
You can publish teams-host.html as another entry point in the Code App bundle. After pa app push, open the official play URL and inspect the actual inner app iframe URL. Preserve its published asset directory and replace only index.html with teams-host.html.
Warning
The observed storageproxy URL is versioned and is a runtime implementation detail, not a documented durable hosting contract. A republish produces a new revision. Confirm HTTP 200 for the wrapper and its assets before packaging, and assess this dependency before production use.
Code Apps enforce CSP by default. Microsoft documents the default frame-ancestors value as:
'self' https://*.powerapps.com
The embedded player must allow every ancestor in the iframe chain. In Power Platform admin center:
- Open Manage > Environments > your environment > Settings > Product > Privacy + Security.
- Under Content security policy, select the App tab for Code Apps.
- Keep CSP enforcement enabled. Preserve reporting and unrelated directives.
- Add only the origins required by your tested Teams clients and wrapper host.
Typical starting values:
| Directive | Additions | When required |
|---|---|---|
frame-ancestors |
https://teams.microsoft.com, https://teams.cloud.microsoft, and SHELL_ORIGIN when different from the Code App origin |
Allows the Code App to be nested under Teams and the wrapper |
frame-src |
https://apps.powerapps.com |
Required on the wrapper's CSP; for a co-hosted wrapper this is the Code Apps environment policy |
connect-src |
Endpoints actually required by your pinned TeamsJS version | Required on the wrapper's CSP; diagnose browser violations before adding |
A field implementation also required https://*.teams.microsoft.com, https://*.cloud.microsoft, and the TeamsJS valid-domains endpoint. Wildcards broaden trust and must be explicitly reviewed rather than copied automatically.
Caution
Code Apps CSP settings are environment-wide. They affect every current and future Code App in that environment, including apps in other solutions. Read the current configuration, merge in place, and write only the approved delta. Never disable CSP or replace the full directive collection with a small example.
Custom values merge with platform defaults, except defaults containing 'none', where custom values replace that default. Allow time for the new response headers to propagate, then inspect the actual served Content-Security-Policy header.
Copy and edit:
templates/manifest.template.json -> teams-package/manifest.json
Replace every {{PLACEHOLDER}}. Generate a new GUID for a new Teams app:
[guid]::NewGuid().ToString()Keep that GUID for future versions. Do not reuse a GUID from another app and do not generate a new GUID for an ordinary update.
Add:
color.png— 192 x 192 pixels;outline.png— 32 x 32 pixels with a transparent background and white outline.
The ZIP root must contain exactly:
manifest.json
color.png
outline.png
Validate the manifest against its declared Microsoft 365 app manifest schema before creating the ZIP. The included script performs structural checks plus an official-schema top-level preflight and packages the files:
./scripts/Build-TeamsPackage.ps1 -PackageDirectory ./teams-package -OutputPath ./dist/my-code-app-teams.zipNote
PowerShell cannot fully validate Microsoft's draft-04 schema with Test-Json. Run full validation with Microsoft 365 Agents Toolkit, Developer Portal for Teams, or another draft-04-compatible validator before upload. The script does not claim full schema conformance.
- Sign in to Teams with a user in the same tenant.
- Open Apps > Manage your apps > Upload an app > Upload a custom app.
- Select the ZIP, choose Add, then Open.
- Verify the installed app inside Teams—not by opening the wrapper directly in a browser.
Start with read-only tests. Confirm:
- expected app name, icon, version, and personal scope;
- TeamsJS initialization and context retrieval;
- wrapper URL and published revision;
- authenticated Power Apps player—not a login or error page;
- normal pointer and keyboard interaction;
- expected records and connector responses;
- safe failure for missing app, environment, or data permissions;
- representative browser Code Apps still work after the environment CSP change.
The iframe load event is not proof that sign-in or the business app succeeded. It can also fire for an error page.
After the pilot and an explicit rollout decision:
- A Teams administrator opens Teams admin center > Teams apps > Manage apps.
- Select Upload new app or Actions > Upload new app and upload the validated ZIP.
- Confirm name, external app ID, version, and Personal scope.
- Configure the approved audience through app availability. For optional installation, do not assign an install or pinning policy.
- Eligible users find it under Apps > Built for your org and choose Add.
A personal scope controls where the app runs; it does not prevent organization-wide catalog availability. Microsoft notes that a new custom app can take a few hours to appear.
For an update:
- Keep the same Teams app GUID.
- Increase the manifest
version. - Publish the Code App and/or wrapper.
- If using the experimental co-hosted method, capture the new wrapper revision and rebuild the package.
- Upload the new package from the existing app's catalog record.
- Re-run the full client and permission matrix.
Keep previous packages, wrapper artifacts, hashes, CSP before/after evidence, and verification notes. Roll back the app package and CSP separately; they are independent changes.
| Symptom | Likely layer | What to inspect |
|---|---|---|
| Teams refuses the ZIP | Package | Declared schema, required properties, icon sizes, files at ZIP root |
| Wrapper never starts | Teams host | app.initialize(), console errors, validDomains, wrapper HTTPS/CSP |
| Browser says “Refused to frame” | CSP | Code App frame-ancestors; wrapper frame-src; all ancestors in the chain |
| TeamsJS request blocked | Wrapper CSP | Exact blocked URL and the pinned TeamsJS version |
| App opens but data fails | Power Platform access | App sharing, license, environment access, Dataverse roles, connector consent |
| Loading layer remains over app | Wrapper CSS | Add .teams-status[hidden] { display: none; } |
| Old UI or 404 wrapper | Release revision | Confirm the actually served Code App revision and wrapper URL |
| Works in web only | Client-specific | Test desktop/mobile, Conditional Access, cookie and iframe behavior separately |
- Use a nonproduction environment first.
- Never place secrets, access tokens, connection strings, or API keys in the wrapper or manifest.
- Do not pass Teams access tokens into the Power Apps iframe.
- Add iframe permissions such as camera, microphone, geolocation, fullscreen, or clipboard only when required.
- Prefer exact origins. Review every wildcard.
- Treat guest access, mobile support, fresh sign-in, writes, file transfer, and notifications as separate test cases.
- A successful admin test is not evidence that a normal user has the required permissions.
prompt.md— execution prompt for an AI coding agent.templates/teams-host.html— Teams wrapper page.templates/src/teams-host.ts— TeamsJS initialization and Power Apps iframe.templates/src/teams-host.css— full-height wrapper and overlay fix.templates/manifest.template.json— personal-tab manifest template.scripts/Build-TeamsPackage.ps1— structural validation and ZIP creation.
- Embed a code app in an iframe
- Configure Content Security Policy for Code Apps
- Teams app package
- Microsoft 365 app manifest schema
- Upload your app in Teams
- Manage custom apps in Teams admin center
MIT