Skip to content
Development
Skill

/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

From plugin
meta-vr
17629 skills1 hook1 MCP
Install
$ npx -y skills add meta-quest/agentic-tools --skill hz-store-pwa --agent claude-code

How 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.md
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
Read more
Ships withmeta-vr

Agentic skills and tools for Meta Quest and Horizon OS development.

Get the whole plugin