Check if user is in VR

How can I detect in my script if a user is actually in VR?

I found that I can use DeviceUtilities.isQuest() or . isDesktop() to check if the user is on the Quest or on a desktop.
And in the same article I found a check if (‘xr’ in navigator), but that gives true when I just opened the scene and am still on the “flat” version, so before I even clicked ‘Enter VR’ on the Quest and also when I am on a desktop.

Context: I am building a sort of hotspot scene, but I want the user position to change. In VR I can use teleporting to move the XRRig, but in a desktop I have to move the FPS Character or camera by script. And I’d like to have the same behaviour in both VR and desktop, so instead of teleporting in VR I want to use a button for both. So when that button is clicked I need to know if have to set the position for the XRRig or the FPS Character/camera.

To detect whether the user is actually in VR (not just on a device that supports it), don’t rely solely on ('xr' in navigator) — that only tells you WebXR is available, not that a session is active. Instead, check the current XR session state.

In Needle Engine, the cleanest approach is to check the NeedleXRSession or the WebXR component’s state. You can do something like:

const xrSession = this.context.xrSession; // or get the WebXR component
if (xrSession && xrSession.session?.isInputSourceActive) {
  // VR mode
}

But the most direct and recommended pattern is to use an XRFlag-style check or hook into the XR session lifecycle. Since you want to move either the XRRig (VR) or the FPS Character/camera (desktop) on button click, the simplest is to check whether a WebXR session is currently presenting. In Needle Engine, you can access the active session via:

const webxr = this.context.scene.getComponent(WebXR);
if (webxr?.session) {
  // VR is active — set XRRig position
} else {
  // Desktop — set FPS character/camera position
}

Or, if you’re using DeviceUtilities, note that isQuest() returning true doesn’t mean the user is in VR — they might be on the flat browser view. So that’s not sufficient. The reliable signal is whether a WebXR session is actively presenting.

If you want your code to react automatically when the user enters or exits VR, use the XR event lifecycle — e.g., listen to sessionchange or the Needle-specific xr-session-start / xr-session-end events. That way, you can track a boolean flag and use it in your button click handler.

Tip: Since your goal is identical behavior in VR and desktop, consider making the button handler check that flag once and then move whichever object is relevant. That avoids the need to branch the teleport logic itself.

Sources: Needle Engine Docs (66%), Needle Engine Docs (63%), Needle Engine Docs (57%), Needle Engine Docs (55%)

1 replies left - Become PRO for more replies.
Reply to this post directly if you need further help (bottom right button)

The webxr check returns ‘undefined’ what is wrong?

The navigator.xr being undefined usually means the browser doesn’t support WebXR at all — or the page’s permissions block it (e.g., inside an iframe without allow="xr"). Even on a Quest, if your scene runs in an iframe where the embedding page doesn’t delegate xr-spatial-tracking, navigator.xr stays undefined.

Don’t check navigator.xr directly. Use Needle Engine’s built-in WebXR component and hook into session events instead — for example, onEnterXR fires when VR/AR starts. That way you get the real session status regardless of browser quirks or permission setup.

If you still need a runtime check, query this.context.xrSession?.session after the WebXR component is added. That’s null until a session activates.

Sources: Needle Engine Docs (59%), Needle Engine Docs (59%), Needle Engine Docs (59%), Needle Engine Docs (59%), Needle Engine Docs (59%), Needle Engine Docs (58%), Needle Engine Docs (57%), Needle Engine Docs (57%), Needle Engine Docs (56%), Needle Engine Docs (55%)

0 replies left - Become PRO for more replies.
Reply to this post directly if you need further help (bottom right button)

Hello,

one easy way would be to use the onEnterXR lifecycle method in your script (just like start or update it gets called by the engine)

Thanks!

Yeah, I saw that suggestion in the assistant’s answer and that works.