Horizon OS Store Setup

Horizon OS is Meta's operating system for Quest headsets, and OpenIAP treats it as an Android build target. Select the horizon platform flavor, provide the Horizon app id from Meta Horizon Developer Hub, and ship a Quest artifact that is separate from your Google Play or Amazon Fire OS artifacts.

Required Values#

Horizon setup has one OpenIAP-specific configuration value. It is the Meta app id for the Horizon app record, not the Android package name.

ValueWhere to get itWhere OpenIAP reads it
Horizon app idMeta Horizon Developer Hub app record. Gradle projects commonly expose it through a placeholder named HORIZON_APP_ID.Expo uses android.horizon.appId. Bare React Native reads a Gradle property named horizonAppId; Flutter reads HORIZON_APP_ID from android/local.properties. Both write Android manifest meta-data com.meta.horizon.platform.HORIZON_APP_ID.
Product SKUsMeta Horizon Developer Hub monetization products.The SKU values passed to fetchProducts, requestPurchase, and Horizon verification.
Verification credentialsYour backend or IAPKit project configuration.Runtime verification payloads use horizon.sku, horizon.userId, and horizon.accessToken when verifying Horizon receipts.

Store Prerequisites#

Complete these once per app in Meta Horizon Developer Hub before configuring any framework:

  1. Register the app in Meta Horizon Developer Hub.
  2. Create your products and subscriptions with stable SKUs — the same strings you will pass to fetchProducts and requestPurchase.
  3. Copy the Horizon app id so the Android manifest can read it at build time.
  4. Prepare a Meta Quest device signed into an account that has access to the app, such as a release-channel member or registered test user.

Framework Setup#

Every framework except Godot ships Quest support through the same Android horizon flavor; only the switch location differs. Find your framework here, then follow the matching section below for full snippets.

FrameworkHow Horizon is selectedApp id location
Native AndroidDepend on openiap-google-horizon or select platform=horizon.Android manifest meta-data.
Expomodules.horizon plus android.horizon.appId in the expo-iap config plugin.The config plugin writes manifest meta-data.
React NativehorizonEnabled=true in Gradle properties.The app writes Android manifest meta-data directly.
FlutterhorizonEnabled=true and app-level missingDimensionStrategy.Gradle manifest placeholder, usually from local properties.
KMPBuild/publish the Android horizonRelease variant.The Android host app owns manifest meta-data.
MAUIBuild Android with OpenIapAndroidStore=horizon, meta, or quest.The Android manifest in the MAUI app owns the app id.
GodotNo dedicated Horizon flavor switch yet, so there is no Godot section below.Not applicable.

Native Android#

Use the Horizon artifact directly, or select the local Gradle flavor when building from source:

kt
dependencies {
    implementation("io.github.hyochan.openiap:openiap-google-horizon:3.5.0")
}

android {
    defaultConfig {
        missingDimensionStrategy("platform", "horizon")
    }
}

Provide the app id in the Android manifest:

xml
<meta-data
    android:name="com.meta.horizon.platform.HORIZON_APP_ID"
    android:value="YOUR_HORIZON_APP_ID" />

Expo#

Expo apps should let the expo-iap config plugin write the Gradle flavor, dependency, and manifest app id during prebuild:

ts
plugins: [
  [
    'expo-iap',
    {
      modules: {
        horizon: true,
      },
      android: {
        horizon: {
          appId: 'YOUR_HORIZON_APP_ID',
        },
      },
    },
  ],
]

React Native#

react-native-iap has no Expo config plugin, so select the Horizon flavor in the app Gradle build and write the app id in the manifest yourself. fireOsEnabled is the switch for Amazon Fire OS builds; the two flavors are mutually exclusive, so keep it false for Quest artifacts:

props
# android/gradle.properties
horizonEnabled=true
fireOsEnabled=false
horizonAppId=YOUR_HORIZON_APP_ID
groovy
// android/app/build.gradle
android {
    defaultConfig {
        def horizonEnabled = project.findProperty('horizonEnabled')?.toBoolean() ?: false
        def fireOsEnabled = project.findProperty('fireOsEnabled')?.toBoolean() ?: false
        if (horizonEnabled && fireOsEnabled) {
            throw new GradleException("horizonEnabled and fireOsEnabled cannot both be true")
        }
        def flavor = fireOsEnabled ? 'amazon' : (horizonEnabled ? 'horizon' : 'play')
        missingDimensionStrategy "platform", flavor

        manifestPlaceholders = [
            HORIZON_APP_ID: project.findProperty('horizonAppId') ?: ''
        ]
    }
}
xml
<meta-data
    android:name="com.meta.horizon.platform.HORIZON_APP_ID"
    android:value="${HORIZON_APP_ID}" />

Flutter#

Flutter uses the same Gradle property model as bare React Native. The app module maps the property into the plugin flavor and injects the app id through a manifest placeholder; localProperties is the loader the Flutter Android template already defines in android/app/build.gradle:

props
# android/gradle.properties
horizonEnabled=true
fireOsEnabled=false
props
# android/local.properties
HORIZON_APP_ID=YOUR_HORIZON_APP_ID
groovy
def horizonEnabled = project.findProperty('horizonEnabled')?.toBoolean() ?: false
def fireOsEnabled = project.findProperty('fireOsEnabled')?.toBoolean() ?: false
def flavor = fireOsEnabled ? 'amazon' : (horizonEnabled ? 'horizon' : 'play')

android {
    defaultConfig {
        missingDimensionStrategy 'platform', flavor
        manifestPlaceholders = [
            HORIZON_APP_ID: localProperties.getProperty("HORIZON_APP_ID") ?: ""
        ]
    }
}

Reference the placeholder in the Android manifest:

xml
<meta-data
    android:name="com.meta.horizon.platform.HORIZON_APP_ID"
    android:value="${HORIZON_APP_ID}" />

KMP and MAUI#

KMP publishes per-store Android variants of the library; Quest apps consume the horizonRelease variant and keep the app id in the Android host app's manifest, exactly as in the Native Android section above. When building the library from source, assemble the variant directly:

sh
./gradlew :library:assembleHorizonRelease

MAUI selects the Horizon AAR flavor with an MSBuild property:

sh
dotnet build -f net10.0-android -p:OpenIapAndroidStore=horizon

Verification#

Client purchase calls stay the same on Quest — fetchProducts and requestPurchase work unchanged. For server validation, pass the Horizon receipt context to Validation or to IAPKit, OpenIAP's hosted verification backend, using the horizon payload:

ts
await verifyPurchase({
  horizon: {
    sku: purchase.productId,
    userId: metaUserId,
    accessToken: horizonAccessToken, // Meta app credential
  },
});

The access token is a Meta app credential. Keep Horizon verification on trusted infrastructure — your backend or IAPKit — rather than embedding the token in the shipped app.