Dashboard

Plug your Quest into this computer with a data-capable USB-C cable, click connect, then accept the "Allow USB debugging" prompt inside the headset.

First time? Setup checklist
  • Enable Developer Mode: create a free developer org at developer.oculus.com, then toggle it in the Meta Quest phone app → Headset Settings.
  • Use a data USB-C cable (many charge-only cables won't work).
  • After clicking connect, put the headset on and accept the USB debugging prompt (tick "Always allow").
  • Close SideQuest / Android Studio — only one ADB client can talk to the headset at once. If stuck, run adb kill-server in a terminal.
Model
Android / OS
Battery
Storage
Sideloaded apps
Serial

Stats refresh on connect.

Game library

Loads a catalog from a public JSON manifest you configure in Settings — download and install straight to the headset. Works fully offline too: without a manifest, use the Install local tab instead.

No catalog loaded yet.

⚠ Only list and install content you own or that is legally free to distribute (open-source games, itch.io freeware, your own builds). You are responsible for what your manifest links to.

WebDAV server

Browse a WebDAV server and install APKs and OBBs straight onto the headset. Large files stream in resumable chunks — nothing is held in memory, so 20 GB OBBs are fine.

Connection

Remember for this tab
Keeps credentials in sessionStorage so a reload doesn't log you out. Cleared when the tab closes. Off = memory only, the safer default.

Code adapter

Paste a snippet that fetches a catalog from wherever it lives and returns a games array. Use this when a source isn't a plain games.json — it needs paging, a custom shape, or a token exchange.

Read this before pasting code

Pasted code runs in a locked sandbox: a null-origin iframe with no DOM access, no access to the headset connection, no access to your saved settings or credentials, and a 30-second time limit. It can only call fetch and return data. QuestLoader validates whatever comes back and does the installing itself — the adapter can never install anything directly.

That protects your computer and your headset. It does not vet what the adapter points at: a returned APK URL is still an APK you're choosing to install. Read the code before you run it, and only run adapters from sources you trust and content you have the right to install.

Adapter contract

Your code is the body of an async function. It receives ctx and must return an array of games (or an object with a games array).

ctx.fetch(url, options)   → normal fetch, CORS rules apply
ctx.log(message)          → writes to the QuestLoader console
ctx.input                 → the text from the "Adapter input" box below

return [
  {
    name:        "My Game",              // required
    packageName: "com.studio.mygame",    // required
    apk:         "https://…/game.apk",   // required, http(s)
    version:     "1.0.0",                // optional
    description: "Short blurb",          // optional
    image:       "https://…/cover.jpg",  // optional
    obbs:        ["https://…/main.1.com.studio.mygame.obb"]  // optional
  }
]

Adapter code

Result

Run an adapter to preview what it returns.

Installed apps

Sideloaded apps on the connected headset. In VR, find them under App Library → dropdown → “Unknown Sources”.

Connect a headset first.

Install from this computer

Install an APK from your hard drive, with optional OBB data files for larger games.

APK file

Drop an .apk here, or click to browse

OBB data files (optional)

Drop .obb file(s) here, or click to browse

OBBs are pushed to /sdcard/Android/obb/<package>/. Standard OBB filenames look like main.1.com.example.game.obb.

Run

Console

Everything QuestLoader does, in order, with any errors.

Fixes

Common failures and exactly what to do about them. Errors in the Console link straight to the relevant fix.

“Unable to claim interface” / another program is using the headset

Only one program can hold the headset's ADB connection at a time. SideQuest, Meta Quest Developer Hub, Android Studio, Unity, scrcpy and Vysor all start a background adb server that grabs it — and quitting the window often leaves that server running.

1 · Quit the apps properly

Close SideQuest, Meta Quest Developer Hub, Android Studio, Unity, scrcpy, Vysor. On macOS use Cmd+Q — closing the window isn't enough.

Also close any other browser tab running QuestLoader. A second tab holds the device just as effectively as another app.

2 · Kill the leftover adb server

Windows — Task Manager (Ctrl+Shift+Esc) → Details tab → find adb.exe → End task. There may be more than one. Or in Command Prompt:

taskkill /F /IM adb.exe

macOS — Terminal:

killall adb

Linux — Terminal:

pkill adb

If you have Android platform-tools installed, this is the polite version on any OS:

adb kill-server

3 · Replug and retry

Unplug the cable, plug it back in, then click Connect on the Dashboard. If it still fails, reboot the headset (hold power → Restart) — that releases the interface for certain.

Stuck on “waiting for headset” / no response after 45 seconds

The headset is asking “Allow USB debugging from this computer?” and nobody has answered it. That prompt only renders inside VR, so it's invisible from your desk.

  1. Put the headset on. This is the whole fix, nine times out of ten.
  2. Look for the dialog. It can sit behind whatever app is open — press the Oculus/Meta button to return to Home if you don't see it.
  3. Tick “Always allow from this computer”, then tap Allow.
  4. Click Connect again on the Dashboard.

No dialog at all? Unplug and replug the cable while wearing the headset — the prompt fires on connect. If it's still absent, your computer's key was probably remembered and then broken: see revoking debugging authorisations.

Quest not listed in the USB picker / “ADB interface NOT FOUND”

Without Developer Mode the headset never exposes its ADB interface, so Chrome genuinely cannot see it.

Enable Developer Mode

  1. Go to developer.oculus.com, sign in, and create an organisation (free, any name — this is what unlocks the toggle).
  2. Open the Meta Horizon / Meta Quest phone app, pair with your headset.
  3. Devices → your headset → Headset Settings → Developer Mode → ON.
  4. Reboot the headset. The toggle doesn't take effect until you do.

Then run Dashboard → Run diagnostics. If it reports ADB interface (FF/42/01): FOUND, you're set.

Also check the headset isn't sitting in charge-only mode — put it on, open the notification panel and select the USB connection, choosing File Transfer (MTP) if prompted.

Nothing appears in the picker at all

Usually the cable. Many USB-C cables — especially the ones bundled with chargers and power banks — carry power only, no data, and behave exactly like a broken port.

  • Use the cable that came with the headset, or a known data cable (a Link cable is ideal).
  • Plug into a port directly on the computer, not a hub, dock or monitor.
  • Try a different port — USB 3 (blue) ports are the safer bet.
  • Quick test: if the headset shows a "USB connected" notification in VR, the cable carries data. If it only charges, it doesn't.

Linux also needs a udev rule before the browser may touch the device:

echo 'SUBSYSTEM=="usb", ATTR{idVendor}=="2833", MODE="0666", GROUP="plugdev"' | sudo tee /etc/udev/rules.d/51-oculus.rules && sudo udevadm control --reload-rules

Then replug the headset.

It used to work / stale debugging keys

The headset remembers which computers it trusts. If that record is stale — you cleared browser data, or paired through SideQuest previously — it can refuse silently and never re-prompt.

  1. In the headset: Settings → System → Developer → Revoke USB debugging authorisations.
  2. Unplug and replug the cable while wearing the headset.
  3. Click Connect here and accept the fresh prompt.

QuestLoader's own key lives in this browser's storage. Clearing site data for this page forces a brand-new key, which triggers a fresh prompt too.

“ADB library FAILED to load” / Console is empty

The page couldn't fetch the ADB library from esm.sh, so nothing can connect.

  • Script/ad blocker (uBlock, Brave Shields, Privacy Badger) — allowlist this page and reload.
  • Content-Security-Policy on your site — if you serve a CSP header, it must permit script-src https://esm.sh and connect-src for your download hosts. This is the most common cause when it works locally but breaks once hosted.
  • Corporate network / firewall blocking esm.sh.
  • Offline — the library is loaded from a CDN, so the page needs internet on first load even for offline installs. To make it truly offline, self-host the library files alongside the page and change the import URLs.
WebUSB unavailable / “requires HTTPS”

WebUSB only runs in a secure context. The page must be served over https:// or from localhost — opening the file directly with file:// will not work.

  • Use desktop Chrome, Edge, or another Chromium browser. Firefox and Safari don't implement WebUSB at all, and there's no workaround.
  • Chrome on iOS/Android won't work either — this needs a desktop.
  • To test locally, serve the folder rather than double-clicking the file:
python3 -m http.server 8000

Then open http://localhost:8000/.

Catalog or download fails with “Failed to fetch”

That's CORS: the browser asked another server for a file and the server didn't say it was allowed. The fix lives on the file host, not here.

  • The host must return Access-Control-Allow-Origin: * (or your domain) on the APK/OBB/JSON files.
  • Hosting the files on the same domain as this page avoids CORS entirely — simplest solution by far.
  • Code adapters run in a null-origin sandbox, so their sources need Access-Control-Allow-Origin: * specifically — an echo of your domain won't match.
  • The URL must be HTTPS. An http:// download from an https:// page is blocked as mixed content.
WebDAV won't connect / “CORS” or PROPFIND errors

A browser is a stricter WebDAV client than a desktop app. The server must explicitly allow this page. Three things must all be true:

  • HTTPS. An http:// server cannot be used from an https:// page.
  • CORS allows this origin, including the PROPFIND method and the Authorization, Depth and Range headers, and it must answer the OPTIONS preflight.
  • Basic auth. Digest cannot work: computing the response needs the server's nonce from the WWW-Authenticate header, which browsers refuse to expose cross-origin. No client-side code can work around this.

nginx

add_header Access-Control-Allow-Origin $http_origin always; add_header Access-Control-Allow-Credentials true always; add_header Access-Control-Allow-Methods "GET,HEAD,OPTIONS,PROPFIND" always; add_header Access-Control-Allow-Headers "Authorization,Depth,Content-Type,Range" always; add_header Access-Control-Expose-Headers "Content-Length,Content-Range,Accept-Ranges,DAV,ETag" always; if ($request_method = OPTIONS) { return 204; }

Apache

Header always set Access-Control-Allow-Origin "https://yoursite.com"

Expose-Headers matters. Without Content-Length and Accept-Ranges exposed, the browser hides them — progress percentages vanish and resume silently turns off, even though downloads still work.

Check it from a terminal

curl -sI -X PROPFIND -u user:pass -H "Depth: 1" https://example.com/files/

A healthy server answers 207 Multi-Status. A 401 means bad credentials; 405 means WebDAV is off or a proxy stripped PROPFIND.

Nextcloud / ownCloud

Use the full WebDAV path, not the web UI URL: https://host/remote.php/dav/files/USERNAME/. Generate an app password (Settings → Security) rather than using your login, and note that Nextcloud does not send CORS headers to arbitrary origins by default — hosting QuestLoader on the same domain avoids the problem entirely.

My games.json won't load / entries get rejected

QuestLoader validates every entry and drops bad ones rather than half-working. The Console lists each rejection with a reason. The usual causes:

  • Not valid JSON — a trailing comma after the last item, single quotes instead of double, or smart quotes from a word processor. Paste the file into jsonlint.com to find the exact line. Edit JSON in a code editor, never in Word or Notes.
  • Got HTML instead of JSON — the URL returned a 404 page. Open the URL directly in a browser tab: you should see raw JSON, not a webpage.
  • “missing or invalid packageName” — must be the real Android package (letters, numbers, dots only), e.g. com.yourstudio.yourgame. Not the game's title.
  • “apk must be an http(s) URL” — must be a full absolute URL starting https://, not /files/game.apk, and it must be a direct file link. Google Drive / Dropbox share pages return an HTML preview, not the APK.
  • Wrong shape — the top level must be {"games":[…]} or a bare array […].

Check your URL from a terminal

curl -sI https://yoursite.com/games.json | grep -i "access-control\|content-type"

You want to see access-control-allow-origin: * and content-type: application/json. No CORS line means the browser will refuse it — see the CORS fix above.

Finding your package name

Install the APK once via the Install local tab, then look at the Installed tab — the exact package name is listed there. Copy it into your JSON.

Install fails after upload
  • INSTALL_FAILED_VERSION_DOWNGRADE — a newer build is installed. Turn on Allow downgrades in Settings, or uninstall it from the Installed tab first.
  • INSTALL_FAILED_UPDATE_INCOMPATIBLE — the APK is signed with a different key than the installed copy. Uninstall the existing one first (this deletes its save data).
  • INSTALL_FAILED_INSUFFICIENT_STORAGE — check the storage meter on the Dashboard.
  • INSTALL_PARSE_FAILED_NO_CERTIFICATES — the APK is unsigned or corrupt. Re-download it.
  • INSTALL_FAILED_NO_MATCHING_ABIS — the APK is for a different CPU architecture. Quest needs an arm64 build.
  • Installs but crashes on launch — it probably needs OBB data files. Use the OBB box on the Install tab, and make sure the package name matches exactly.

Settings

Settings are saved in this browser when possible.

Catalog source

Online mode
Fetch the game catalog from a public JSON manifest URL. Turn off to use QuestLoader fully offline (local APK/OBB installs only).

The endpoint must allow CORS (GitHub raw/releases and most static hosts do). Same-origin URLs like /games.json on this site always work.

Manifest format (example games.json)
{
  "name": "My Quest Catalog",
  "version": 1,
  "games": [
    {
      "name": "My Cool Game",
      "packageName": "com.mystudio.coolgame",
      "version": "1.2.0",
      "description": "Short blurb shown on the card.",
      "image": "https://yoursite.com/img/coolgame.jpg",
      "apk": "https://yoursite.com/files/coolgame.apk",
      "obbs": [
        "https://yoursite.com/files/main.12.com.mystudio.coolgame.obb"
      ]
    }
  ]
}

Only name, packageName and apk are required. obbs is an optional array of OBB file URLs.

Install behavior

Grant all permissions on install
Adds -g to pm install so games don't nag for permissions in-headset.
Allow downgrades
Adds -d so an older version can replace a newer installed one.

About

QuestLoader runs entirely in your browser using WebUSB and an open-source JavaScript implementation of the ADB protocol (ya-webadb). Nothing is uploaded anywhere — files go straight from your computer to the headset over the cable. Requires HTTPS and Chrome or Edge on desktop.