Skip to content

Platform in Platforms

PWAs on Meta Quest (Horizon OS browser)

Published Updated

Meta Horizon OS supports web-based 2D experiences that can be distributed through the Meta Horizon Store. Meta documents Bubblewrap as the tool that packages a PWA into an Android App Bundle and launches its web content through a Trusted Web Activity (Start Building with Meta Quest 3S, Meta Horizon OS Developers).

Meta documents Meta Horizon Store distribution for web-based 2D experiences on Meta Horizon OS. For immersive features, MDN marks XRSystem.isSessionSupported() as limited availability and available only in secure contexts in supporting browsers; the method checks whether the user’s WebXR device supports a requested session mode (Start Building with Meta Quest 3S, Meta Horizon OS Developers; XRSystem: isSessionSupported() method, MDN).

A Trusted Web Activity opens PWA content from an Android app through a protocol based on Custom Tabs. Digital Asset Links verify that the Android app and the site come from the same developer, while the user’s browser renders the web content (Trusted Web Activity overview, Chrome for Developers).

The example keeps ordinary page content available unless the browser reports support for an immersive VR session.

Reveal an immersive control after checking support

Section titled “Reveal an immersive control after checking support”

Paste this complete page into an HTTPS origin. It checks for navigator.xr, handles both the unsupported and rejected paths, and leaves the non-immersive content visible.

<!doctype html>
<html lang="en">
<body>
<p>This content works without an immersive session.</p>
<button id="enter-xr" hidden>Enter XR</button>
<output id="xr-status">Checking WebXR support...</output>
<script>
const button = document.querySelector('#enter-xr');
const status = document.querySelector('#xr-status');
if (!('xr' in navigator)) {
status.value = 'Continue without immersive mode.';
} else {
navigator.xr.isSessionSupported('immersive-vr').then((supported) => {
button.hidden = !supported;
status.value = supported
? 'Immersive mode is available.'
: 'Continue without immersive mode.';
}).catch(() => {
button.hidden = true;
status.value = 'Continue without immersive mode.';
});
}
</script>
</body>
</html>

The button remains hidden when navigator.xr is absent, the requested session mode is unsupported, or the support check rejects.

A Trusted Web Activity host can pass data to its web content in a URL, including through query parameters (Trusted Web Activity overview, Chrome for Developers). Save this page as launch.html, then open launch.html?source=quest.

<!doctype html>
<html lang="en">
<body>
<output id="launch-source">No launch source supplied.</output>
<script>
const source = new URLSearchParams(location.search).get('source');
if (source) {
document.querySelector('#launch-source').value = `Launched from ${source}.`;
}
</script>
</body>
</html>

The output reads Launched from quest. when the query parameter is present and retains the fallback text when it is absent.

Specifications

SpecificationStatus
None.