The site has a new content architecture. We've added the ability to select your development device to show device-specific content. Please read our blog post Oculus Developer Center Update: Device-centric Documentation Architecture for more information.

Oculus Quest Development

All Oculus Quest developers MUST PASS the concept review prior to gaining publishing access to the Quest Store and additional resources. Submit a concept document for review as early in your Quest application development cycle as possible. For additional information and context, please see Submitting Your App to the Oculus Quest Store.

Interact with VR controllers

You can use window.navigator.getGamepads() to query for the Oculus Quest controllers (Oculus Touch), Oculus Go controller, the Gear VR touchpad, controller and/or gamepad, and any Bluetooth gamepads.

Oculus Touch controllers for Oculus Quest are exposed as two gamepads with containing “Oculus Touch (Left)” and “Oculus Touch (Right)” strings. Note, these are the same ids that Google Chrome uses for Oculus Touch controllers on PC with Oculus Rift. The layout of the buttons is also fully compatible between Quest and Rift controllers. If the user’s code need to distinguish Quest’s controllers from the Rift’s ones (for example, to render proper controller model) then it should query the headset id via VRDisplay.displayName: for Oculus Quest it will contain the “Oculus Quest” string.

Oculus Touch controllers are 6DOF ones, meaning besides orientation the pose also contains position (see below). Therefore, no ‘arm model’ is required for these controllers.

On Oculus Go device, the controller is exposed as a single gamepad object, where field is set to “Oculus Go Controller”. Only pose.orientation is populated: user’s code is responsible for adding the ‘arm model’ logic, if necessary.

On Gear VR devices, the touchpad and buttons on the headset are exposed as a gamepad with the property of “Gear VR Touchpad”. An external controller is exposed as a separate object with the property of “Gear VR Controller”. Only pose.orientation is populated for the Gear VR controller: user’s code is responsible for adding the ‘arm model’ logic, if necessary.

The Mobile SDK has a great example (OVR_ArmModel.cpp) of an arm model that can be used for 3DoF controllers such as the Gear VR and Oculus Go controllers.

Gamepad object description

The Gamepad object may contain the following properties reported:

  • is the name of the controller. Rendering the correct controller model and position is vital to help with user immersion and this field, along with the VRDisplay.displayName will provide comprehensive information on which model needs to be rendered.

  • Gamepad.hand returns which hand the controller is held in; always draw the controller in the correct location. Note: this is an extension to the Gamepad API and a subject to change at any time.

  • Gamepad.pose returns the current pose of the controller(s). This always contains orientation field, which is a quaternion, similar to one that is used for getting orientation of the HMD. If the controllers are 6DOF (Oculus Touch with Quest) then the pose also contains non-zero position, which is reported in the same coordinate space as the position of the HMD. Note: this is an extension to the Gamepad API and a subject to change at any time. Here is an example on how the pose can be converted to a matrix (using ‘gl-matrix’ framework):
    function getPoseMatrix (out, pose, hand) {
    orientation = (pose) ? pose.orientation : null;
    position = (pose) ? pose.position : null;
    if (!orientation) { orientation = [0, 0, 0, 1]; }
    if (!position) {
      // If this is a gamepad without a pose set it out in front of us so
      // we can see it.
      if (hand == "left") {
          position = [-0.3, 0, -0.3];
      else if (hand == "right") {
          position = [0.3, 0, -0.3];
      else {
          position = [0.1, -0.1, -0.5];
    if (vrDisplay.stageParameters) {
      mat4.fromRotationTranslation(out, orientation, position);
      mat4.multiply(out, vrDisplay.stageParameters.sittingToStandingTransform, out);
    } else {
      vec3.add(standingPosition, position, [0, PLAYER_HEIGHT, 0]);
      mat4.fromRotationTranslation(out, orientation, standingPosition);
    var gamepadMat = mat4.create();
    getPoseMatrix(gamepadMat, gamepad.pose, gamepad.hand);
    • Besides orientation and position, Oculus Touch, Oculus Go and Gear VR controllers also may report the following information:
      • linear_acceleration - a 3D vector (x,y,z) indicating linear acceleration for all three axes;
      • angular_acceleration - a 3D vector (x,y,z) indicating angular acceleration for all three axes;
      • linear_velocity - a 3D vector (x,y,z) indicating linear velocity for all three axes;
      • angular_velocity - a 3D vector (x,y,z) indicating angular velocity for all three axes.
  • Gamepad.axes - reports X-Y position of the joystick or X-Y position on trackpad/touchpad, i.e. axes.length == 2. Each axis is reported in the range [-1.0 .. 1.0]
    • 0 - horizontal (X) trackpad / joystick, where -1.0 indicates left side, 0.0 - center, 1.0 - right side;
    • 1 - vertical (X) trackpad / joystick, where -1.0 indicates the joystick is fully pushed forward, or a finger at the top border of the trackpad, 0.0 - center, 1.0 - the joystick is fully pushed backward, or a finger at the bottom border of the trackpad.

The following table summarizes how these values map to swipes.

AxisIf Value = -1If Value = 1
0Swipe leftSwipe right
1Swipe upSwipe down
  • Gamepad.buttons reports state of the buttons. Note, for the Gear VR touchpad the gamepad’s button 0 will indicate a short press whenever the user taps the touchpad. The mapping of the buttons is a follows:

| Index | Button mapping | Capacitive | Analog | Presented | |-|-|-|-|-| | 0 | trackpad / thumb / joystick press | true | false | All | 1 | trigger / indextrigger | true for Touch | true | All | 2 | grip | true | true | Touch | 3 | X or A | true | false | Touch | 4 | Y or B | true | false | Touch | 5 | reserved | false | false | Touch

    • ‘Capacitive’ means the GamepadButton.touched boolean property is reported;
    • ‘Analog’ means the GamepadButton.value boolean property is reported in range [0..1.0], where 0.0 corresponds to no press and 1.0 to full pressed state.

Gamepad events

Currently VR controllers fire two GamepadEvents - gamepadconnected and gamepaddisconnected.

The gamepadconnected connected event for VR controllers is fired after entering VR is requested.

The gamepaddisconnected for VR controllers is fired after an experience finishes its immersive session.

window.addEventListener("gamepadconnected", onGamepadConnected);
window.addEventListener("gamepaddisconnected", onGamepadDisconnected);

function onGamepadConnected() {
function onGamepadDisconnected() {


Oculus Touch controllers are capable of producing vibrations as a haptic feedback. This is an extension to the Gamepad API and a subject to change at any time. The Gamepad object may contain a hapticActuators that is a read-only property of the Gamepad interface; it returns an array containing GamepadHapticActuator objects, each of which represents haptic feedback hardware available on the controller. Currently only one actuator per controller is supported and only for Oculus Touch with Oculus Quest.

The GamepadHapticActuator.pulse() can be used to perform the haptic feedback. For example:

if ("hapticActuators" in gamepad && gamepad.hapticActuators.length > 0) {
  for (var j = 0; j < gamepad.buttons.length; ++j) {
    if (gamepad.buttons[j].pressed) {
      // Vibrate the gamepad using to the value of the button as
      // the vibration intensity, normalized to 0.0..1.0 range.
      gamepad.hapticActuators[0].pulse((j + 1.0) / (gamepad.buttons.length + 1), 1000);

The first parameter of the GamepadHapticActuator.pulse() is the intensity of the pulse. This takes a value between 0.0 (no intensity) and 1.0 (full intensity).

The second parameter is the duration of the pulse, in milliseconds.

Code example

Try out an interactive VR controllers sample which demonstrates how they work here. Full source is available here