Use the published Meta Quest Bubblewrap CLI to create an Android project from a hosted web app manifest. These instructions were verified with @meta-quest/bubblewrap-cli version 1.24.1. This version requires Node.js 18 or later and installs the bubblewrap command.
Before packaging the PWA, create its app in the Developer Dashboard. Follow Create apps to create the app page and Meta Horizon Application ID.
The Meta Horizon Application ID is required if a WebXR PWA uses in-app purchases. You can find it in the URL when viewing the app in Developer Dashboard.
Install Bubblewrap
Install the published CLI package:
npm install --global @meta-quest/bubblewrap-cli
Confirm that the bubblewrap command is available:
bubblewrap --version
On its first run, Bubblewrap can download compatible Java Development Kit (JDK) and Android command-line tools. Unless your development environment manages these dependencies, allow Bubblewrap to install the versions it requests.
Prepare monetization
If your WebXR PWA uses in-app purchases (IAP), complete the current setup in In-app purchases in WebXR PWAs before packaging the app.
During Meta Quest initialization, Bubblewrap asks whether to include Horizon Billing. If you enable it, Bubblewrap also asks for the Meta Horizon Application ID and adds the billing configuration to the generated project. Have the application ID from the IAP setup ready before you run bubblewrap init.
Initialize the project
Create an empty project directory:
mkdir my-pwa
cd my-pwa
Initialize the project with the public URL of your manifest:
Bubblewrap opens an interactive prompt for the Android project configuration. Review the following settings carefully.
App mode
Select 2D for a windowed site, including screen-based 3D, or immersive for an app that launches directly into WebXR. In the generated twa-manifest.json, this setting is stored in horizonOSAppMode.
Android package identifier
Choose a unique Android package identifier. Every update to an existing Store app must use the same package identifier.
Display mode
For a 2D app, use standalone display mode. Choose the orientation that matches the app, such as landscape for a wide panel or portrait for a vertical panel.
Horizon Billing
Enable Horizon Billing only for a WebXR PWA that uses in-app purchases. When billing is enabled, enter the Meta Horizon Application ID from Developer Dashboard.
Configure signing
The Meta Horizon Store requires a digitally signed package. During initialization, select an existing signing key or let Bubblewrap create one.
For an update to an existing app, use the same signing key as the previous version. For a new app, keep the generated keystore file, alias, and passwords in a secure location. Every future update must use the same signing certificate.
For more information about keystores and certificates, see Sign your app.
Configure Digital Asset Links
A Trusted Web Activity uses Digital Asset Links to verify the relationship between the Android package and the web app. An immersive PWA does not launch if this verification fails. A 2D PWA displays custom tab UI when its web origin is not verified.
Get the SHA-256 fingerprint for the certificate that signs the package:
Copy the SHA256 fingerprint from the output, then add it to the Bubblewrap project:
bubblewrap fingerprint add <sha256-fingerprint>
This command updates the project’s fingerprint list and generates assetlinks.json. Publish that file on the web app’s origin at:
https://example.com/.well-known/assetlinks.json
Confirm that the URL returns the generated JSON over HTTPS before testing the package. See the Digital Asset Links documentation for protocol details.
If the app uses more than one trusted origin, publish /.well-known/assetlinks.json on the primary origin and every additional trusted origin. Each hosted file must contain the package name and SHA-256 signing certificate fingerprint for the PWA.
If keytool is not available on your shell path, use the executable in the JDK installed by Bubblewrap or another compatible JDK.
Multiple PWAs on one origin
An assetlinks.json file is an array and can authorize more than one Android package on the same web origin. Include one statement for each package and signing certificate:
Enter the signing passwords when prompted. After the build finishes, confirm that app-release-signed.apk exists in the project directory.
Sideload the package
Connect a developer-mode Meta Quest device, then install the signed APK with Android Platform Tools:
adb install /path/to/app-release-signed.apk
After installation, open the app from the Unknown Sources section of the headset’s app library.
Troubleshooting
Trusted Web Activity verification fails
Look for TWA verification was unsuccessful! in the device log. Confirm that the package name and SHA-256 signing certificate fingerprint in each hosted assetlinks.json match the package you installed. Also confirm that the file is available at /.well-known/assetlinks.json on the primary origin and every additional trusted origin.
The same verification failure causes a 2D PWA to display a URL bar or other custom tab UI.
The package opens in the wrong app mode
Open twa-manifest.json and confirm that horizonOSAppMode matches the app mode selected during initialization. Run bubblewrap build again after changing the project configuration.
The Bubblewrap command does not start
Run node --version and confirm that Node.js 18 or later is active. Reinstall @meta-quest/bubblewrap-cli after switching Node.js versions.
The Android Gradle plugin reports an incompatible Java version
Allow Bubblewrap to install its compatible JDK, or update the jdkPath setting in the Bubblewrap configuration to a compatible JDK. Remove an obsolete jdkPath value to let Bubblewrap prompt for the dependency again.