Develop

Create a new app

Updated: Aug 25, 2026
This page gets you to a basic Android app on Meta Horizon OS. Start from the template, then work through the platform limits below.

Start from the template

Horizon OS template running in Meta Spatial Simulator
We recommend starting with the Horizon OS template. It gives you a project that already declares the Horizon OS requirements, and it has two routes to the same starter — a single-activity Jetpack Compose app:
The template can be run in Meta Spatial Simulator or on your headset.
If you are not using the template, make sure your AndroidManifest.xml declares the following. These are the minimum Horizon OS requires to treat your activity as a VR panel and keep it usable in headset:
AreaManifest declarationWhy it matters
Horizon OS SDK
<horizonos:uses-horizonos-sdk horizonos:minSdkVersion="69" horizonos:targetSdkVersion="69" />
Min and target SDK Horizon OS requires
Supported devices
<meta-data android:name="com.oculus.supportedDevices" android:value="quest2|questpro|quest3" />
Declares Quest 2, Pro, 3 as targets
Default panel size
android:defaultWidth="1024dp" android:defaultHeight="640dp" inside <layout>
Launch size for your resizable panel
Minimum panel size
android:minWidth="360dp" android:minHeight="225dp" inside <layout>
Smallest size the system may shrink you to for field-of-view; keeps you usable on gaze-and-hands
VR intent category
<category android:name="com.oculus.intent.category.VR" />
Marks the activity as VR-capable
Orientation and display
android:screenOrientation and window metadata
Handles panel resizing and immersive display

Unsupported dependencies

Quest devices do not include Google Mobile Services, so calls into GMS APIs fail. Here are some recommended replacements for popular services:
GMS dependencyReplacement
gms.auth (Sign-In)
gms.location
gms.ads.identifier (AdID)
billingclient.api
Android Notifications
Quest devices support all Firebase dependencies that do not require Google Mobile Services. See Dependencies of Firebase Android SDKs on Google Play Services for more information.
The following Firebase libraries are unsupported:
Firebase libraryReplacement
App Check Play Integrity provider
App Check SafetyNet provider
App Indexing
Dynamic Links
Cloud Messaging
ML Vision
None
ML Custom Model
None

Unsupported permissions

Horizon OS prohibits many Android permissions — dangerous ones like INSTALL_PACKAGES or headset-meaningless ones like CALL_PHONE. A build that requests a prohibited permission fails store upload. Other permissions are allowed after review; you explain the use case at submission.
For the complete lists, see Prohibited Android permissions and Review-requiring Android permissions. Remove prohibited permissions from AndroidManifest.xml before you build. The Android Studio plugin underlines unsupported libraries and permissions as you type.

Run one codebase on multiple platforms

Ship one codebase to Quest and phones by keeping unsupported code out of the Horizon OS build. Use one of the two options below – they solve the same problem differently and you do not need both.
ApproachUse when
Product flavors
You want to strip mobile-only dependencies from Quest build at compile time
Runtime detection
You need one artifact for both platforms – it decides whether you are on Horizon OS or a phone at runtime. This avoids maintaining two APKs

Option 1: Product flavors – separate APKs

Build separate APKs per device family. Each flavor (app/src/quest/, app/src/mobile/) has its own manifest and dependencies merged with main at compile time.
The following declares flavor-specific dependencies – for example, use mobileImplementation for Google Play Billing and questImplementation for Horizon Billing. The Android Studio plugin can generate this structure for you.
// app/build.gradle.kts
dependencies {
    "mobileImplementation"(libs.billing)
    "questImplementation"(libs.horizon.billing.compatibility)
}
See Android’s product flavors and manifest merge guides.

Option 2: Runtime detection – single APK

Keep a single APK and decide at runtime whether you are on Horizon OS or a phone. This avoids maintaining two APKs.
Add the support library from Maven Central – this dependency provides HorizonOsDetector:
dependencies { implementation("com.meta.horizonosx:core:0.1.0") }
if (HorizonOsDetector.isOnHorizonOs(context)) {
    // Horizon OS only
} else {
    // Mobile only
}
Guard the whole call chain so GMS classes are never loaded on Horizon OS.

Troubleshooting

Product flavors – active variant and merging

Android Studio can only track one active product flavor at a time. It ignores source files in inactive flavors and does not analyze them for errors. If your Quest flavor files show as unresolved or your build still pulls in mobile-only libraries:
  • Open the Build Variants tool window in Android Studio and switch the active variant to questDebug or mobileDebug as needed – changing the variant re-syncs Gradle.
  • Keep common code in app/src/main/ and flavor-specific code in app/src/quest/ and app/src/mobile/. For example, keep BillingUser.kt in main and provide quest/java/Billing.kt using Horizon Billing and mobile/java/Billing.kt using Google Play Billing with the same interface.
  • For flavor-specific dependencies, use questImplementation / mobileImplementation instead of implementation (see example in Option 1: Product flavors).
  • For manifests, remember Android’s merge rules: put Quest-only metadata in quest/AndroidManifest.xml, mobile-only permissions in mobile/AndroidManifest.xml, and shared data in main/AndroidManifest.xml.

Runtime detection – library not found or guarded code still crashes

  • If HorizonOsDetector is not found, ensure you added com.meta.horizonosx:core from Maven Central (see Option 2: Runtime detection) and re-synced Gradle.
  • Always pass an Android Context to isOnHorizonOs(context) and guard the whole call chain – do not just guard the final API call, guard the import path that would load GMS classes.

Build failures after porting

If you used the AI porting flow and the build fails, share the error with the AI – it diagnoses SDK version mismatches and missing Maven repo declarations. Verify that build.gradle contains the Meta Maven repository and correct minSdkVersion / targetSdkVersion (see Recommended manifest setup).