Teams カスタムアプリのテスト用アップロード(サイドロード)で「アプリが一覧に出ない」「Something went wrong」だけで原因が掴めない——そんなときは、manifest.json の“事前検証”と“切り分けの型”を作るのが最短ルートです。本記事では失敗パターンを体系化し、公式ツールでの静的検証から、ZIP パッケージ構成、権限・ドメイン設定、ログ採取、CI 自動化まで、公開前に不具合を潰し切るための実践手順を詳説します。
現象の整理:なぜ「アップロードに失敗する」のか
Teams へのカスタムアプリのサイドロードが失敗する主因は、概ね次の5領域に収斂します。
- manifest.json の不備(必須フィールド欠落、誤値、スキーマ不整合、バージョン不一致、JSON 文法エラー)
- ZIP パッケージ構成ミス(ルート直下に3ファイルが無い、アイコンサイズやファイル名不一致、余計なフォルダ)
- テナント/クライアント側の制約(カスタムアプリ許可ポリシー、App Permission/Setup Policy、クライアント差分)
- ホスト側設定の欠落(validDomains 不備、CSP/Frame-ancestors 不備、HTTPS 証明書、SSO 連携の整合性)
- キャッシュ/バージョン更新忘れ(
versionを上げていない、アイコンや manifest のキャッシュが残存)
「曖昧なエラー」で止まる場合でも、以下の“事前検証 & 切り分け”の型に沿えば、原因領域を高速に特定できます。
静的検証で落とせる不具合はすべて落とす
まずは、コードを書き換える前に manifest を機械的に検証します。手元の感覚では、サイドロード失敗の半数以上は静的検証だけで潰せます。
公式ツールでのバリデーション
- Teams Toolkit for Visual Studio Code:コマンドパレットから Validate Manifest。VS Code 上でスキーマに基づく型チェックと必須項目の検証を行います。
- Microsoft 365 CLI(m365):ターミナルで manifest を検証できます。
m365 teams manifest validate --path ./manifest.json
- Developer Portal(旧 App Studio):App validation で同等の検証と、アイコンのプレビュー、フィールド単位のエラー表示が可能です。
VS Code の JSON スキーマ検証を効かせる
manifest の先頭に $schema を付けると、エディタが補完と型検証を行います(例)。
{
"$schema": "https://developer.microsoft.com/json-schemas/teams/v1.16/MicrosoftTeams.schema.json",
"manifestVersion": "1.16",
"id": "00000000-0000-0000-0000-000000000000",
...
}
JSON にコメントや末尾カンマは使えません。// コメントや trailing comma は厳禁です。
ZIP パッケージの正解パターン
サイドロードに使う ZIP は ルート直下 に次の3ファイルが必要です。サブフォルダを切ると失敗します。
| ファイル | 要件 | チェックポイント |
|---|---|---|
manifest.json | UTF-8(BOM 無推奨) | 必須項目・文法・スキーマ一致 |
| カラーアイコン | PNG 192×192 px | 透過・アルファ有は可/ファイル名は manifest と一致 |
| アウトラインアイコン | PNG 32×32 px | 単色推奨/背景は透過 |
ZIP 作成時は OS が混入させるリソース(例:macOS の __MACOSX、.DS_Store)を含めないでください。CI で zip を生成する場合は、明示的に3ファイルのみを追加しましょう。
アップロード前に必ず通す「プリフライト」チェックリスト
| 項目 | 確認ポイント | NG の例 | OK の例 |
|---|---|---|---|
manifestVersion | サポート対象か(例:1.16)。 | 古い 1.5 固定のまま | 1.16 に更新し、スキーマも一致 |
id / packageName / version | GUID/逆ドメイン表記/セマンティックバージョン。 | 同じ version のまま再アップロード | 1.2.3 → 1.2.4 に更新 |
| アイコン | サイズ・拡張子・ファイル名一致。 | icon.png と manifest の参照名が不一致 | color.png / outline.png を参照どおり配置 |
developer | 名称・URL・プライバシー・利用規約の有無。 | 空文字・ダミー URL | 実ドメインの https URL を設定 |
validDomains | タブ/認証/静的ファイルの配信元を網羅。 | 末尾スラッシュ混在、ワイルドカード記法 | "example.com", "cdn.example.com" |
scopes × 機能 | Bot/Tab/ME の対象範囲が矛盾しない。 | 個人タブなのに team のみ | ["personal"] のみ、など整合 |
| URL とプロトコル | すべて https/リダイレクト先も含め https。 | http://localhost を本番 manifest に残存 | 開発用と本番用で manifest を分離 |
| SSO・AAD 整合 | webApplicationInfo の App ID / リソース一致。 | ID の桁数/ハイフン違い | アプリ登録の値をコピペで正確に反映 |
| CSP / フレーム埋め込み | frame-ancestors に Teams 系ドメイン。 | X-Frame-Options: DENY | Content-Security-Policy: frame-ancestors https://*.teams.microsoft.com https://*.office.com https://*.skype.com; |
アップロード先での切り分け:Web ↔ デスクトップ ↔ モバイル
同じ ZIP を以下の順序で試験し、再現範囲を絞り込みます。
- Teams Web 版(ブラウザ):キャッシュや更新が軽く、エラーメッセージが出やすい。
- Teams デスクトップ クライアント:Web と差が出たらクライアント固有の要因(Runtime、キャッシュ)を疑う。
- Teams モバイル:モバイル限定の制約(一部タブの非対応など)を確認。
Web で成功・デスクトップで失敗の場合は、アプリのキャッシュクリア(サインアウト→再サインイン、アプリの再読み込み)とアイコンのバージョン衝突を最優先で確認します。
管理ポリシーと権限:サイドロードの前提を満たしているか
テナントやユーザーに適用されるポリシーで、カスタムアプリのアップロード自体が禁止されているケースは珍しくありません。次を確認してください。
- アプリ許可ポリシー:サードパーティ/カスタムアプリが許可になっているか。
- アプリ設定ポリシー(セットアップ):ユーザーが「アプリのアップロード」を実行できるか。
- ストアの可視性:組織ストア/個人アップロードの経路が UI 上に表示されているか。
ポリシー変更は反映に時間差が出る場合があります。待機に頼らず、別ユーザー/別ポリシーでの再現比較が有効です。
「Something went wrong」から原因を掘り起こすログ採取術
- 開発者コンソール:Teams で
Ctrl + Alt + Shift + I。Console と Network で manifest 解析、アイコン取得、タブ URL ロード、CSP エラーの痕跡を確認。 - ログの迅速保存:
Ctrl + Alt + Shift + 1でクライアントログをダウンロード(成功時は保存通知)。 - Developer Portal の検証結果:App validation のエラーログでフィールド単位の指摘を参照。
- Teams 管理センターのアプリ診断:アップロード失敗のイベント履歴と失敗コードを確認。
ログにはテナント情報や App ID が含まれるため、共有時は必ずマスクしましょう(ドメイン・GUID・内部 URL)。
代表的なエラーと即効リカバリー
| 症状 | 一次切り分け | 修正方針(最短) |
|---|---|---|
| アプリが一覧に出ない | ポリシーで非表示/ZIP ルート構成ミス | 許可ポリシーの見直し、ZIP を 3ファイル直下で再作成 |
| Something went wrong(汎用) | manifest の JSON 文法/必須フィールド | m365 と Toolkit の二段バリデーション、$schema 有効化 |
| タブ起動で白画面 | CSP frame-ancestors/HTTPS | Teams/Office ドメインを許可、自己署名証明書を回避 |
| SSO が動かない | webApplicationInfo 不一致 | AAD App ID と Application ID URI を manifest と一致させる |
| アイコンだけ更新されない | キャッシュ/version 未更新 | version をインクリメント、再サインイン・再読み込み |
実例で学ぶ:最小構成 manifest(個人タブ)
{
"$schema": "https://developer.microsoft.com/json-schemas/teams/v1.16/MicrosoftTeams.schema.json",
"manifestVersion": "1.16",
"version": "0.1.0",
"id": "11111111-1111-1111-1111-111111111111",
"packageName": "com.example.minimaltab",
"developer": {
"name": "Example Inc.",
"websiteUrl": "https://www.example.com",
"privacyUrl": "https://www.example.com/privacy",
"termsOfUseUrl": "https://www.example.com/terms"
},
"name": { "short": "Minimal Tab", "full": "Minimal Personal Tab" },
"description": {
"short": "最小構成の個人タブ",
"full": "検証用の最小構成個人タブ。HTTPS と validDomains のみを要件に含む。"
},
"icons": { "color": "color.png", "outline": "outline.png" },
"accentColor": "#404040",
"staticTabs": [{
"entityId": "home",
"name": "Home",
"contentUrl": "https://app.example.com/home",
"websiteUrl": "https://app.example.com",
"scopes": ["personal"]
}],
"validDomains": ["app.example.com"]
}
この段階では Bot やメッセージ拡張は含めません。段階的に機能を増やすことで、障害範囲を限定できます。
CSP とフレーム埋め込みの落とし穴
Teams のタブは iFrame でホストされるため、Content-Security-Policy の frame-ancestors で Teams ドメインを許可していないと白画面になります。代表例:
Content-Security-Policy: frame-ancestors https://*.teams.microsoft.com https://*.skype.com https://*.office.com;
X-Frame-Options は DENY/SAMEORIGIN を使わないでください。Azure Front Door/CDN を挟む場合も、最終応答ヘッダに適切な値が出ているかを実応答で確認します。
ドメインと HTTPS:validDomains と証明書の整合
- validDomains はホスト名だけを列挙(スキーム/パスは不要)。
- ワイルドカード不可。
*.example.comは使えないため、必要なサブドメインを明示列挙。 - 本番は必ず公開 CA の証明書。自己署名は開発中のローカルでのみ許容に留める。
- 認証用や静的配信用の別ホスト(
auth.example.com、cdn.example.com)も漏れなく記載。
SSO(Azure AD)連携の“最低限”整えるチェック
- manifest の
webApplicationInfo.idは Azure AD アプリ登録の アプリケーション(クライアント)ID と一致。 - 必要に応じて Application ID URI / スコープ名の整合(Graph や自前 API)。
- タブ SSO のリダイレクト URI は SPA/WEB の正しい種類で登録。
- 有効な発行者・オーディエンス(aud)で受け取れているかを実トークンで検証。
SSO 失敗は UI では“沈黙”しやすく、Network タブの 401/403 と WWW-Authenticate の詳細が頼りです。
Web とデスクトップで差が出るときの対策
- キャッシュ:デスクトップはキャッシュが強く、
versionを上げずに差し替えると古い manifest が残りがちです。 - ランタイム差:ブラウザ拡張や実験的フラグの影響で Web のみ挙動が変わることがあります。シークレットウィンドウで再現を確認。
- OS 差:Windows/Mac で描画・フォント・IME の副作用が出ることも。最小構成のタブで比較検証しましょう。
CI による自動検証:人手のバラツキを排除する
手元での検証に加え、プルリクごとに manifest を自動検証・パッケージングすると、ヒューマンエラーを抑えられます。例(GitHub Actions):
name: Validate & Package Teams App
on: [pull_request]
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install m365 CLI
run: npm i -g @pnp/cli-microsoft365
- name: Validate manifest
run: m365 teams manifest validate --path ./appPackage/manifest.json
- name: Package zip
run: |
cd appPackage
zip -j ../dist/app.zip manifest.json color.png outline.png
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: teams-app
path: dist/app.zip
これでレビュー段階で 必ず検証が走り、ZIP も“正しい構成”で生成されます。
デバッグを加速する小技集
- バージョンを自動採番:ビルド番号やコミットハッシュで
versionを毎回更新し、キャッシュ不整合を抑止。 - 開発用 manifest と本番用 manifest を分離:
validDomainsと URL を切り替え、混入を防止。 - 「最小再現」manifest を持つ:Bot/ME を外した最小タブで、環境依存とアプリ依存を切り分け。
- ネットワーク記録の保存:HAR で 4xx/5xx、CSP ヘッダ、リダイレクトチェーンを共有できる形に。
よくある質問(FAQ)
Q. Developer Portal のプレビューでは動くのに、サイドロードで失敗します。
ZIP のルート構成とアイコン名一致、version 更新を確認。Developer Portal のプレビューは一部検証を“スキップ”するため、ZIP 作成ミスが温存されることがあります。
Q. validDomains に *.example.com は使えますか?
使えません。必要なサブドメインを個別列挙してください。
Q. http://localhost を使った開発は?
ローカル開発では許容される場面もありますが、本番 manifest からは除去し、HTTPS の動作確認を本番同等で先に済ませるべきです。
Q. 画像だけ更新が反映されません。
version を上げて再アップロードしてください。クライアントキャッシュにより、manifest の変更が流れてもアイコンだけ古いまま残ることがあります。
Q. エラーが英語で曖昧です。どこを見れば良いですか?
Ctrl + Alt + Shift + I の Console と Network に出る JSON パースエラーや CSP 違反、manifest.json 取得の 404/403 を優先的に確認します。
「チェックしたつもり」を無くす最終点検表(印刷推奨)
| カテゴリ | 確認項目 | OK/NG |
|---|---|---|
| スキーマ | $schema と manifestVersion が一致している | |
| 識別子 | id は GUID、packageName は逆ドメイン、version は更新 | |
| アイコン | 192/32px PNG、ファイル名と manifest の参照名が一致 | |
| ドメイン | validDomains にタブ/認証/CDN のホストを漏れなく列挙 | |
| HTTPS | 本番系 URL はすべて HTTPS(証明書は公開 CA) | |
| 機能と範囲 | Bot/Tab/ME の scopes が仕様と整合 | |
| SSO | webApplicationInfo と AAD 登録の ID/URI が一致 | |
| CSP | frame-ancestors に Teams/Office/Skype ドメイン | |
| ZIP | ルート直下が manifest.json / color.png / outline.png の 3ファイルのみ | |
| ポリシー | ユーザーの許可ポリシーでカスタムアプリが許可 |
まとめ:サイドロードは「検証→打鍵→観察」の高速ループで
manifest の 静的検証(Teams Toolkit / m365)と ZIP 構成の固定化、Web→デスクトップ→モバイルの順に切り分ける型、そして CSP・validDomains・SSO の3点セット——この流れをひとつの“儀式”にすれば、曖昧なエラーで足止めされる時間は激減します。最後は CI に検証とパッケージングを任せ、人がやるのは原因仮説の立案と観察だけにしましょう。アップロード前に異常の種を取り除ければ、組織ストア公開や審査対応もスムーズに進みます。
付録:失敗しないためのテンプレートとコマンド集
最小タブの雛形(差分用)
{
"$schema": "https://developer.microsoft.com/json-schemas/teams/v1.16/MicrosoftTeams.schema.json",
"manifestVersion": "1.16",
"version": "0.0.1",
"id": "22222222-2222-2222-2222-222222222222",
"packageName": "com.example.sanitycheck",
"developer": {
"name": "Sanity Co.",
"websiteUrl": "https://sanity.example.com",
"privacyUrl": "https://sanity.example.com/privacy",
"termsOfUseUrl": "https://sanity.example.com/terms"
},
"name": { "short": "Sanity", "full": "Sanity Check Tab" },
"description": { "short": "検証用", "full": "manifest チェック用最小タブ" },
"icons": { "color": "color.png", "outline": "outline.png" },
"accentColor": "#6264A7",
"staticTabs": [{
"entityId": "sanity",
"name": "Sanity",
"contentUrl": "https://sanity.example.com",
"scopes": ["personal"]
}],
"validDomains": ["sanity.example.com"]
}
m365 CLI コマンド(よく使うもの)
# manifest の検証
m365 teams manifest validate --path ./manifest.json
# ZIP の作成(3ファイルのみ)
zip -j ./dist/app.zip ./manifest.json ./color.png ./outline.png
問題報告時の秘匿ルール(セキュリティ)
- 顧客ドメイン、GUID、内部 URL、トークン/クッキーは必ずマスク。
- ログ/HAR は必要最小限を共有、保管期限と廃棄手順を明記。

コメント