Skip to content

Latest commit

 

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Embed Code Apps in Teams

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.

Architecture

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.

What this does not grant

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.

Prerequisites

  • 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

1. Add the Teams wrapper

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-js

Add 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 build

2. Host the wrapper

Recommended: stable HTTPS hosting

Deploy 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.

Experimental: co-host in the Code App bundle

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.

3. Configure Code Apps CSP

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:

  1. Open Manage > Environments > your environment > Settings > Product > Privacy + Security.
  2. Under Content security policy, select the App tab for Code Apps.
  3. Keep CSP enforcement enabled. Preserve reporting and unrelated directives.
  4. 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.

4. Create the Teams package

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.zip

Note

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.

5. Pilot in Teams

  1. Sign in to Teams with a user in the same tenant.
  2. Open Apps > Manage your apps > Upload an app > Upload a custom app.
  3. Select the ZIP, choose Add, then Open.
  4. 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.

6. Publish to the organization catalog

After the pilot and an explicit rollout decision:

  1. A Teams administrator opens Teams admin center > Teams apps > Manage apps.
  2. Select Upload new app or Actions > Upload new app and upload the validated ZIP.
  3. Confirm name, external app ID, version, and Personal scope.
  4. Configure the approved audience through app availability. For optional installation, do not assign an install or pinning policy.
  5. 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.

7. Update and roll back

For an update:

  1. Keep the same Teams app GUID.
  2. Increase the manifest version.
  3. Publish the Code App and/or wrapper.
  4. If using the experimental co-hosted method, capture the new wrapper revision and rebuild the package.
  5. Upload the new package from the existing app's catalog record.
  6. 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.

Troubleshooting

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

Security notes

  • 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.

Repository contents

Official references

License

MIT

About

Guide and reusable templates for embedding Power Apps Code Apps in Microsoft Teams personal tabs.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages