/hz-store-pwa
Guides shipping a web app to the Meta Quest and Horizon OS Store as a PWA/TWA — both 2D windowed panels and immersive WebXR/VR. Covers building the web app (IWSDK for WebXR, any responsive PWA for 2D), Vercel deploy, web app manifest + icons, the WebXR-only auto-enter-session
$ npx -y skills add meta-quest/agentic-tools --skill hz-store-pwa --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
- Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
- You can call itInvoke it directly when you want it.
- Slash command
/hz-store-pwa
Context preview
The summary Claude sees to decide when to auto-load this skill.
Guides shipping a web app to the Meta Quest and Horizon OS Store as a PWA/TWA — both 2D windowed panels and immersive WebXR/VR. Covers building the web app (IWSDK for WebXR, any responsive PWA for 2D), Vercel deploy, web app manifest + icons, the WebXR-only auto-enter-session
SKILL.md
hz-store-pwa.SKILL.mdname: hz-store-pwa
license: Apache-2.0
description: Guides shipping a web app to the Meta Quest and Horizon OS Store as a PWA/TWA — both 2D windowed panels and immersive WebXR/VR. Covers building the web app (IWSDK for WebXR, any responsive PWA for 2D), Vercel deploy, web app manifest + icons, the WebXR-only auto-enter-session step, choosing 2D vs immersive mode in @meta-quest/bubblewrap-cli, keystore/Digital-Asset-Links, and ovr-platform-util Store upload. Use before any IWSDK/WebXR build, PWA packaging, bubblewrap, or Horizon Store upload work.
allowed-tools: Bash(npx:*) Bash(npm:*) Bash(curl:*) Bash(bubblewrap:*)
Store PWA/TWA Skill
Guide the end-to-end process of wrapping a web app as a Meta Quest app and shipping it to the Meta Horizon Store. This skill covers both delivery modes — a **2D windowed panel** and an **immersive WebXR/VR** experience — through the same pipeline: build the web app, deploy to Vercel, add a PWA manifest + icons, package as a signed Quest APK with `@meta-quest/bubblewrap-cli`, and upload with `ovr-platform-util`.
Commands use `<…>` tokens (e.g. `<DOMAIN>`, `<HORIZON_APP_ID>`, `<team-slug>`, `<PW>`) — substitute your own values before running.
When to Use This Skill
Use this skill when you need to:
- Ship a web app (2D or WebXR) to the Meta Horizon Store as a PWA/TWA
- Decide whether an app should run as a 2D panel or an immersive WebXR session
- Build a WebXR app with IWSDK and wire up auto-enter-session for the installed PWA
- Deploy a PWA to Vercel and produce a valid, installable web app manifest + icons
- Package a live PWA into a signed Quest APK with `@meta-quest/bubblewrap-cli`
- Configure the signing keystore and Digital Asset Links so the TWA will launch
- Upload a build to the Store with `ovr-platform-util`
- Troubleshoot a 2D app stuck loading, an immersive app showing a URL bar, a TWA
that won't launch, or an upload that's blocked
For deeper IWSDK app-building guidance, see the `hz-iwsdk-webxr` skill. For the broader Store submission process (VRC compliance, store assets, review tracking), see the `hz-store-submit` skill.
Pipeline Overview
The full pipeline follows this order. The two mode-specific deltas are flagged; all other steps are identical for 2D and immersive.
0. Pick app mode → 2D panel vs immersive WebXR (sets steps 1 + 4)
1. Build the web app → IWSDK WebXR app (immersive) OR any responsive PWA (2D)
2. Deploy to Vercel → public HTTPS origin = <DOMAIN>
3. Manifest + icons → installable web app manifest, PNG icons, live on <DOMAIN>
4. Package as APK → bubblewrap: keystore, twa-manifest, build, asset links
5. Upload to the Store → ovr-platform-util upload-quest-build
Dependencies between steps matter — see [Order of Operations](#order-of-operations) at the end.
Step 0: Pick the App Mode First
The app mode is the single most important decision, chosen once. It changes exactly two things downstream:
1. Whether the web app auto-enters a WebXR session on launch (immersive only). 2. The `horizonOSAppMode` value in `twa-manifest.json` (`"immersive"` vs `"2D"`).
| | **2D PWA** | **Immersive WebXR PWA** | |---|---|---| | Runs as | windowed 2D panel on Horizon | enters a full VR/WebXR session | | Web app | any responsive PWA (IWSDK optional) | WebXR app (IWSDK is the easy path) | | Auto-enter `requestSession` | **NO — do not add it** (Step 1) | **YES — built into the app** (Step 1) | | `horizonOSAppMode` | `"2D"` (Step 4) | `"immersive"` (Step 4) |
A wrong `horizonOSAppMode` value is the classic failure mode: a 2D app set to `immersive` is stuck loading; an immersive app set to `2D` shows a browser URL bar.
See [`references/app-modes.md`](references/app-modes.md) for the full decision guide.
Step 1: Build the Web App
Immersive WebXR app (IWSDK)
Scaffold with `@iwsdk/create` (the only supported scaffolder):
npx @iwsdk/create@latest <app-name> --yes --mode vr --no-metaspatial \
--no-physics --no-locomotion --grabbing
Toggle `--physics` (Havok gravity/collisions), `--locomotion` (roam a large space), and `--grabbing` (hands/controllers pick objects up) to fit the app. For arcade-style apps prefer deterministic manual motion over physics.
**Don't reinvent IWSDK app code.** The template's bundled `CLAUDE.md`, `.claude/skills/iwsdk-*` skills, and the `iwsdk-rag` MCP are the source of truth for imports, ECS, XR input, physics, UI, and debugging. Query those rather than guessing.
**Build auto-enter into the immersive app from the start.** An installed immersive PWA opens with no 2D page, so the app itself must start the session on load (the app-icon tap is the user activation). Gate it on `getDigitalGoodsService` so it runs only in the installed PWA, never a browser tab:
const nav = navigator as Navigator & { xr?: { isSessionSupported?: (m:string)=>Promise<boolean> } };
if ("getDigitalGoodsService" in window && nav.xr?.isSessionSupported) {
nav.xr.isSessionSupported("immersive-vr")
.then(s => { if (s) world.launchXR(); }) // IWSDK launchXR == requestSession + setup
.catch(() => {});
}`getDigitalGoodsService` is device-only — validate this path on the headset.
2D windowed app
Any responsive web app/PWA works — IWSDK is not required. It runs as a single- instance standalone panel with its own Library entry. Make sure it's a valid installable PWA (Step 3) and build/deploy it like any static/SPA site (Step 2). Do **NOT** add the auto-enter code above.
Full scaffolding flags, project layout, and the auto-enter rationale are in [`references/app-modes.md`](references/app-modes.md).
Step 2: Deploy to Vercel
The web app must be live on a public HTTPS origin before packaging — `bubblewrap` fetches the manifest and icons from it. Set `base: "./"` in your Vite config, then:
npx -y vercel@latest whoami
npx -y vercel@latest teams ls
npx -y vercel@latest deploy --prod --yes --scope <team-slug>
Two URLs result:
- **Canonic
Read more
name: hz-store-pwa license: Apache-2.0 description: Guides shipping a web app to the Meta Quest and Horizon OS Store as a PWA/TWA — both 2D windowed panels and immersive WebXR/VR. Covers building the web app (IWSDK for WebXR, any responsive PWA for 2D), Vercel deploy, web app manifest + icons, the WebXR-only auto-enter-session step, choosing 2D vs immersive mode in @meta-quest/bubblewrap-cli, keystore/Digital-Asset-Links, and ovr-platform-util Store upload. Use before any IWSDK/WebXR build, PWA packaging, bubblewrap, or Horizon Store upload work. allowed-tools: Bash(npx:*) Bash(npm:*) Bash(curl:*) Bash(bubblewrap:*)
Store PWA/TWA Skill
Guide the end-to-end process of wrapping a web app as a Meta Quest app and shipping it to the Meta Horizon Store. This skill covers both delivery modes — a **2D windowed panel** and an **immersive WebXR/VR** experience — through the same pipeline: build the web app, deploy to Vercel, add a PWA manifest + icons, package as a signed Quest APK with `@meta-quest/bubblewrap-cli`, and upload with `ovr-platform-util`.
Commands use `<…>` tokens (e.g. `<DOMAIN>`, `<HORIZON_APP_ID>`, `<team-slug>`, `<PW>`) — substitute your own values before running.
When to Use This Skill
Use this skill when you need to:
- Ship a web app (2D or WebXR) to the Meta Horizon Store as a PWA/TWA
- Decide whether an app should run as a 2D panel or an immersive WebXR session
- Build a WebXR app with IWSDK and wire up auto-enter-session for the installed PWA
- Deploy a PWA to Vercel and produce a valid, installable web app manifest + icons
- Package a live PWA into a signed Quest APK with `@meta-quest/bubblewrap-cli`
- Configure the signing keystore and Digital Asset Links so the TWA will launch
- Upload a build to the Store with `ovr-platform-util`
- Troubleshoot a 2D app stuck loading, an immersive app showing a URL bar, a TWA
that won't launch, or an upload that's blocked
For deeper IWSDK app-building guidance, see the `hz-iwsdk-webxr` skill. For the broader Store submission process (VRC compliance, store assets, review tracking), see the `hz-store-submit` skill.
Pipeline Overview
The full pipeline follows this order. The two mode-specific deltas are flagged; all other steps are identical for 2D and immersive.
0. Pick app mode → 2D panel vs immersive WebXR (sets steps 1 + 4) 1. Build the web app → IWSDK WebXR app (immersive) OR any responsive PWA (2D) 2. Deploy to Vercel → public HTTPS origin = <DOMAIN> 3. Manifest + icons → installable web app manifest, PNG icons, live on <DOMAIN> 4. Package as APK → bubblewrap: keystore, twa-manifest, build, asset links 5. Upload to the Store → ovr-platform-util upload-quest-build
Dependencies between steps matter — see [Order of Operations](#order-of-operations) at the end.
Step 0: Pick the App Mode First
The app mode is the single most important decision, chosen once. It changes exactly two things downstream:
1. Whether the web app auto-enters a WebXR session on launch (immersive only). 2. The `horizonOSAppMode` value in `twa-manifest.json` (`"immersive"` vs `"2D"`).
| | **2D PWA** | **Immersive WebXR PWA** | |---|---|---| | Runs as | windowed 2D panel on Horizon | enters a full VR/WebXR session | | Web app | any responsive PWA (IWSDK optional) | WebXR app (IWSDK is the easy path) | | Auto-enter `requestSession` | **NO — do not add it** (Step 1) | **YES — built into the app** (Step 1) | | `horizonOSAppMode` | `"2D"` (Step 4) | `"immersive"` (Step 4) |
A wrong `horizonOSAppMode` value is the classic failure mode: a 2D app set to `immersive` is stuck loading; an immersive app set to `2D` shows a browser URL bar.
See [`references/app-modes.md`](references/app-modes.md) for the full decision guide.
Step 1: Build the Web App
Immersive WebXR app (IWSDK)
Scaffold with `@iwsdk/create` (the only supported scaffolder):
npx @iwsdk/create@latest <app-name> --yes --mode vr --no-metaspatial \ --no-physics --no-locomotion --grabbing
Toggle `--physics` (Havok gravity/collisions), `--locomotion` (roam a large space), and `--grabbing` (hands/controllers pick objects up) to fit the app. For arcade-style apps prefer deterministic manual motion over physics.
**Don't reinvent IWSDK app code.** The template's bundled `CLAUDE.md`, `.claude/skills/iwsdk-*` skills, and the `iwsdk-rag` MCP are the source of truth for imports, ECS, XR input, physics, UI, and debugging. Query those rather than guessing.
**Build auto-enter into the immersive app from the start.** An installed immersive PWA opens with no 2D page, so the app itself must start the session on load (the app-icon tap is the user activation). Gate it on `getDigitalGoodsService` so it runs only in the installed PWA, never a browser tab:
const nav = navigator as Navigator & { xr?: { isSessionSupported?: (m:string)=>Promise<boolean> } };
if ("getDigitalGoodsService" in window && nav.xr?.isSessionSupported) {
nav.xr.isSessionSupported("immersive-vr")
.then(s => { if (s) world.launchXR(); }) // IWSDK launchXR == requestSession + setup
.catch(() => {});
}`getDigitalGoodsService` is device-only — validate this path on the headset.
2D windowed app
Any responsive web app/PWA works — IWSDK is not required. It runs as a single- instance standalone panel with its own Library entry. Make sure it's a valid installable PWA (Step 3) and build/deploy it like any static/SPA site (Step 2). Do **NOT** add the auto-enter code above.
Full scaffolding flags, project layout, and the auto-enter rationale are in [`references/app-modes.md`](references/app-modes.md).
Step 2: Deploy to Vercel
The web app must be live on a public HTTPS origin before packaging — `bubblewrap` fetches the manifest and icons from it. Set `base: "./"` in your Vite config, then:
npx -y vercel@latest whoami npx -y vercel@latest teams ls npx -y vercel@latest deploy --prod --yes --scope <team-slug>
Two URLs result:
- **Canonic
Agentic skills and tools for Meta Quest and Horizon OS development.
Repo: meta-quest/agentic-tools
Other skills on meta-vr.
- /hz-android-2d-porting
Guides porting existing Android 2D apps to Meta Quest and Horizon OS — input adaptation, panel layout, and design requirements. Use when adapting a mobile Android app for Quest.
Open skill - /hz-api-upgrade
Upgrades Meta Quest apps to newer Horizon OS SDK versions — migration guides, deprecated API replacements, changelog. Use when updating SDK versions or fixing deprecated API warnings.
Open skill - /hz-immersive-designer
Guides design of comfortable, intuitive VR/MR experiences for Meta Quest and Horizon OS — comfort guidelines, interaction patterns, spatial layout, accessibility. Use during UX design review or when evaluating comfort and accessibility.
Open skill - /hz-iwsdk-webxr
Builds WebXR experiences for Meta Quest and Horizon OS using the Immersive Web SDK (IWSDK) — ECS architecture, Three.js integration, spatial UI. Use when creating web-based VR/MR apps for Quest Browser.
Open skill - /hz-new-project-creation
Scaffolds new Meta Quest and Horizon OS projects with recommended settings for Unity, Unreal, Android/Spatial SDK, or WebXR. Use when creating a new Quest app from scratch.
Open skill - /hz-perfetto-debug
Analyzes Meta Quest and Horizon OS VR performance using Perfetto traces — frame timing, CPU/GPU bottlenecks, render pass analysis. Use when profiling frame drops, jank, or thermal issues on Quest devices.
Open skill

