Meta VR OS NSDK project setup
Updated: Sep 4, 2026
The Meta VR OS Native Software Development Kit (NSDK) lets you access Horizon OS system APIs from native languages such as C and C++.
It’s distributed as an AAR package containing:
- Header files: C API declarations to include at compile time, such as
<metavr/versioning.h> and <metavr/common.h>. - Stub shared library (
libhzos.meta.so): a minimal shared object that provides symbol definitions for the linker at build time. It contains no real implementation.
At runtime, the operating system on the device supplies the real libhzos.meta.so with full implementations. The stub is never packaged into your APK. It exists only so the build system can resolve symbols during linking.
- Android Studio Ladybug (2024.2) or newer
- Android Gradle Plugin 8.9 or higher
- CMake 3.22.1 or higher (installable through Android Studio SDK Manager)
- To install: Go to Tools > SDK Manager > SDK Tools and check CMake.
- Android NDK (installable through Android Studio SDK Manager)
- To Install: In the same SDK Tools tab, check NDK (Side by side).
- Gradle 8.13 or higher
- A physical Meta Quest device running Horizon OS.
Note: While these instructions demonstrate using NSDK via Android Studio, CMake, and Gradle, NSDK’s stub shared C library can be integrated into your project via other build and dependency management systems.
This guide walks through setting up a new Android Studio project that uses the Meta VR OS NSDK. You have two options:
- Option A: Gradle dependency (recommended). The Meta VR OS NSDK AAR is published on Maven Central with Prefab metadata. Gradle automatically exposes the native headers and stub library to CMake. This is the simplest path with fewer manual steps.
- Option B: Manual setup. Download the AAR, extract headers and the stub library into a local directory, and configure CMake by hand. Use this approach for offline development or when you need to customize the library layout.
Both options produce the same result: your native code links against the Meta VR OS NSDK stub at build time and calls the real library on the device at runtime.
Note that the Manual Setup steps also work for a project that uses CMake directly, without Gradle.
Step 1: Create a new Android Studio project
Open Android Studio and select File > New > New Project.
- Under the Phone and Tablet category, choose Empty Activity. Quest devices run Android, so you can use standard Android templates. Later steps walk you through adding native C++ and CMake configuration.
- Set:
- Language: Kotlin (or Java)
- Minimum SDK: API 34
- Build configuration language: Kotlin DSL (
build.gradle.kts)
Click Finish.
Note your project name and package name. You’ll use them in the CMake configuration and JNI function signatures in later steps. This guide uses myapp and com.example.myapp as examples; replace them with your actual values.
- Once the project is created, follow Option A or Option B below to integrate the Meta VR OS NSDK.
Option A: Gradle dependency (recommended)
Step A1: Add the Maven Central repository
In your project’s settings.gradle.kts, make sure mavenCentral() is listed under dependencyResolutionManagement.repositories:
dependencyResolutionManagement {
repositories {
google()
mavenCentral() // Required for Meta VR OS NSDK
}
}
In your version catalog (gradle/libs.versions.toml), add the Meta VR OS NSDK library:
[versions]
metavr-os-nsdk = "207" # Replace with latest Meta VR OS NSDK version
[libraries]
metavr-os-nsdk = { group = "com.meta.metavr", name = "metavr-os-nsdk", version.ref = "metavr-os-nsdk" }
Then in app/build.gradle.kts, add the dependency:
dependencies {
implementation(libs.metavr.os.nsdk)
}
Update your app/build.gradle.kts to enable Prefab, set the ABI filter, and exclude the stub from the APK:
android {
// ... existing config ...
buildFeatures {
prefab = true
}
defaultConfig {
// Only arm64-v8a is supported on Meta Quest devices.
ndk {
abiFilters.clear()
abiFilters += listOf("arm64-v8a")
}
}
// Exclude the stub .so from the APK. The real library is on the device.
packaging {
jniLibs {
excludes += listOf("**/libhzos.meta.so")
}
}
externalNativeBuild {
cmake {
path = file("src/main/cpp/CMakeLists.txt")
version = "3.22.1"
}
}
}
Create or update app/src/main/cpp/CMakeLists.txt. With Prefab, you use find_package instead of manually declaring an imported library:
cmake_minimum_required(VERSION 3.22.1)
project("myapp")
# Prefab exposes the Meta VR OS NSDK package automatically.
find_package(metavr-os-nsdk REQUIRED CONFIG)
add_library(${CMAKE_PROJECT_NAME} SHARED
nsdk-example.cpp
)
target_link_libraries(${CMAKE_PROJECT_NAME}
android
log
metavr-os-nsdk::hzos
)
Note: You don’t need to create a separate metavr_os_nsdk_lib/ directory, write a CMake file for the imported library, or manually set include directories. Prefab handles all of this through the Gradle dependency. The hzos module exports the metavr_headers module, so linking metavr-os-nsdk::hzos makes the headers available too.
The AAR also exposes a header-only Prefab module, metavr_headers. Linking it adds the <metavr/...> include path to your target without putting libhzos.meta.so on the link line:
target_link_libraries(${CMAKE_PROJECT_NAME}
android
log
metavr-os-nsdk::metavr_headers
)
Use this module when:
- A target includes the headers but calls no API, such as a shared interface layer or a bindings generator. Linking the stub there adds a dependency the target has no use for.
- You build a variant for another ABI. The AAR carries the stub for
arm64-v8a alone, so metavr-os-nsdk::hzos fails to resolve in an x86_64 variant. The headers resolve for every ABI. - You resolve the API at runtime. Linking
metavr-os-nsdk::hzos records libhzos.meta.so as a load-time dependency of your library, so System.loadLibrary fails on any device that does not supply that library. Linking metavr-os-nsdk::metavr_headers instead takes the declarations from the headers and leaves you to resolve the entry points through dlopen and dlsym, so your app starts and takes a fallback path when the library is absent. Meta VR OS NSDK entry points are C functions, so dlsym finds them under the names the headers declare.
Use this approach if you need to work offline, customize the library layout, or can’t use Gradle’s Prefab integration.
Download the latest Meta VR OS NSDK AAR from Maven Central or your artifact repository.
- Configure the Maven Central repository in your project as described in Step A1: Add the Maven Central repository.
- Declare the Meta VR OS NSDK dependency as described in Step A2: Add the Meta VR OS NSDK dependency.
- Copy the
.aar file to your project. Rename the extension from .aar to .zip and extract it to get the headers and stub library.
Create a directory called metavr_os_nsdk_lib/ at the root of your project. This directory contains the NSDK headers and stub library. The structure should be:
metavr_os_nsdk_lib/
├── CMakeLists.txt
├── include/
│ └── metavr/
│ ├── common.h
│ └── versioning.h
│ └── ...
└── stubs/
└── arm64-v8a/
└── libhzos.meta.so (stub, build-time only)
After renaming and decompressing the AAR file, copy the relevant contents into metavr_os_nsdk_lib/.
Note: The internal layout of the AAR doesn’t match the directory structure above. After extracting, create the include/ and stubs/ directories manually, then copy the headers from prefab/modules/metavr_headers/include/ and the stub library from prefab/modules/hzos/libs/android.arm64-v8a/ into the corresponding locations. Rename android.arm64-v8a to arm64-v8a when creating the stubs/ subdirectory. The final structure must match the layout shown above.
Create metavr_os_nsdk_lib/CMakeLists.txt with the following content:
cmake_minimum_required(VERSION 3.22.1)
project("metavr_os_nsdk")
# Declare the Meta VR OS NSDK stub as an imported shared library.
add_library(hzos.meta SHARED IMPORTED GLOBAL)
# Point to the ABI-specific stub.
set_target_properties(hzos.meta PROPERTIES
IMPORTED_LOCATION ${CMAKE_CURRENT_SOURCE_DIR}/stubs/${ANDROID_ABI}/libhzos.meta.so
)
# Expose the NSDK headers to consumers.
# SYSTEM suppresses compiler warnings originating from these headers.
target_include_directories(hzos.meta SYSTEM
INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include
)
Create or update app/src/main/cpp/CMakeLists.txt:
cmake_minimum_required(VERSION 3.22.1)
project("myapp")
# Import the Meta VR OS NSDK library sub-project.
# Path is relative to this CMakeLists.txt (app/src/main/cpp/).
add_subdirectory(../../../../metavr_os_nsdk_lib metavr_os_nsdk_lib_binary)
add_library(${CMAKE_PROJECT_NAME} SHARED
nsdk-example.cpp
)
target_link_libraries(${CMAKE_PROJECT_NAME}
android
log
hzos.meta
)
Note: The ../../../../metavr_os_nsdk_lib path is relative to the location of this CMakeLists.txt file (app/src/main/cpp/). If you place your CMakeLists.txt at a different directory depth, adjust the relative path accordingly.
Update your app/build.gradle.kts to enable CMake and exclude the stub from the APK:
android {
// ... existing config ...
defaultConfig {
// Only arm64-v8a is supported on Meta Quest devices.
ndk {
abiFilters.clear()
abiFilters += listOf("arm64-v8a")
}
}
// Exclude the stub .so from the APK. The real library is on the device.
packaging {
jniLibs {
excludes += listOf("**/libhzos.meta.so")
}
}
externalNativeBuild {
cmake {
path = file("src/main/cpp/CMakeLists.txt")
version = "3.22.1"
}
}
}
Once CMake and Gradle are configured, follow the
Common Steps to declare the Meta VR OS SDK version, write native code, and build/run on a Quest device.
Common steps (both options)
Add the metavr XML namespace and declare your Meta VR OS SDK version requirements in app/src/main/AndroidManifest.xml:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:metavr="http://schemas.meta.com/metavr-sdk"
xmlns:tools="http://schemas.android.com/tools">
<metavr:uses-metavr-sdk
metavr:minSdkVersion="66"
metavr:targetSdkVersion="207" />
<application ...>
<!-- your activities -->
</application>
</manifest>
The minSdkVersion declares the lowest Horizon OS version your app supports. The targetSdkVersion declares the version your app was designed and tested against. These are Horizon OS version numbers, not Android API levels.
Horizon OS still accepts the deprecated <horizonos:uses-horizonos-sdk> element, but <metavr:uses-metavr-sdk> should be used for new builds.
Verifying your device’s Horizon OS version: On your Quest device, go to Settings > General > Software Update to see the current OS version. You can also query it programmatically through ADB:
adb shell getprop ro.vros.build.version
Make sure the reported version meets or exceeds your minSdkVersion.
Note on the metavr XML namespace: The <metavr:uses-metavr-sdk> element is unique to Horizon OS. It is referenced by the Meta Horizon Store and by Horizon OS when your app is installed and when it runs. Standard Android build tooling (Android Gradle Plugin and AAPT2) preserves it in the merged manifest but may emit an unknown-namespace warning during build, which is safe to ignore. To verify it’s present in your final APK, inspect the merged manifest:
# After building, check the merged manifest:
cat app/build/intermediates/merged_manifests/debug/AndroidManifest.xml | grep metavr
Create your C++ source file (such as app/src/main/cpp/nsdk-example.cpp):
#include <jni.h>
#include <string>
#include <metavr/versioning.h>
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_myapp_MainActivity_getMetaVrOsSdkVersion(
JNIEnv* env,
jobject /* this */) {
std::string version = std::to_string(MvrOS_getVersion());
return env->NewStringUTF(version.c_str());
}
Note: The JNI function name must match your app’s package name. Replace com_example_myapp with your actual package, converting dots to underscores (for example, package com.mycompany.xrapp becomes Java_com_mycompany_xrapp_MainActivity_getMetaVrOsSdkVersion).
Load the library and display the version from your Kotlin activity. The default Empty Activity template uses Jetpack Compose, so you can pass the version string to the generated Greeting composable:
class MainActivity : ComponentActivity() {
external fun getMetaVrOsSdkVersion(): String
companion object {
init {
System.loadLibrary("myapp")
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val version = getMetaVrOsSdkVersion()
enableEdgeToEdge()
setContent {
MyappTheme {
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
Greeting(
name = "Meta VR OS SDK Version: $version",
modifier = Modifier.padding(innerPadding)
)
}
}
}
}
}
Connect a Meta Quest device through USB or use adb connect over Wi-Fi. Select your Quest device as a target in Android Studio. Click Run or use ./gradlew assembleDebug from the terminal. The app installs and launches on the device.
If CMake reports that it can’t find the metavr-os-nsdk package when using the Gradle/Prefab approach:
- Make sure
prefab = true is set under buildFeatures in your app/build.gradle.kts. - Run File > Sync Project with Gradle Files and wait for sync to complete before building.
- Verify that the dependency version in
libs.versions.toml matches an available release on Maven Central. - Check that
mavenCentral() is listed in your settings.gradle.kts repositories.
Build fails with “undefined reference to MvrOS_...”
The linker can’t find Meta VR OS NSDK symbols. Check that:
- Option A:
find_package(metavr-os-nsdk REQUIRED CONFIG) is present in your CMakeLists.txt and you are linking against metavr-os-nsdk::hzos (not bare hzos). The headers are included transitively. metavr-os-nsdk::metavr_headers on its own supplies the headers and no symbols, so a target that calls the API needs metavr-os-nsdk::hzos. - Option B:
metavr_os_nsdk_lib/CMakeLists.txt exists and correctly declares the hzos.meta imported library. The stub .so is present at metavr_os_nsdk_lib/stubs/arm64-v8a/libhzos.meta.so. Your app’s CMakeLists.txt includes add_subdirectory for metavr_os_nsdk_lib and lists hzos.meta in target_link_libraries.
The compiler can’t find the Meta VR OS NSDK headers. Verify that:
- Option A: Gradle sync completed successfully and
prefab = true is enabled. The find_package call automatically sets up include paths. - Option B: The headers exist under
metavr_os_nsdk_lib/include/metavr/. The target_include_directories in metavr_os_nsdk_lib/CMakeLists.txt points to the correct path.
App crashes at runtime with UnsatisfiedLinkError
This typically means the native library failed to load. Common causes include:
- Missing System.loadLibrary call: Make sure your activity’s companion object or static block loads the library.
- Wrong library name: The name passed to
System.loadLibrary must match the project() name in your app’s CMakeLists.txt (without the lib prefix and .so suffix). - ABI mismatch: Make sure
abiFilters is set to arm64-v8a and you’re running on a real Quest device (not an x86 emulator).
App crashes with “cannot locate symbol MvrOS_...”
The stub .so was accidentally packaged into the APK, or the device doesn’t have the required Horizon OS version. Check that:
packaging.jniLibs.excludes includes "**/libhzos.meta.so" in your build.gradle.kts.- The device’s Horizon OS version meets or exceeds the value of
metavr:minSdkVersion declared in your app’s AndroidManifest.xml. <metavr:uses-metavr-sdk> is declared in your manifest. Without it, the Meta Horizon Store may ship your binary to devices that don’t meet your minimum Horizon OS version, producing this symptom in the field. Sideloaded builds are unaffected — the element is not enforced at install or runtime.
Gradle sync fails or CMake is not found
- Install CMake 3.22.1 or higher through Tools > SDK Manager > SDK Tools > CMake.
- Make sure the
cmake.version in your build.gradle.kts matches an installed version.
Build stalls during Android NDK download
Gradle may automatically download and install the Android NDK if one isn’t already present. This is a large package (around 1.5 GB) and Gradle doesn’t show download progress, so the build can appear frozen. To avoid this, either wait for the download to complete (check for network or disk activity) or install the NDK manually through Tools > SDK Manager > SDK Tools before building. You can also pin a specific NDK version in app/build.gradle.kts:
android {
ndkVersion = "27.0.12077973" // Use an NDK version already installed
}
Only arm64-v8a is supported
Meta Quest devices exclusively use 64-bit ARM processors. Don’t add other ABIs, such as armeabi-v7a or x86_64, to abiFilters. They fail to link against the stub and aren’t supported.