Ir al contenido

Build an extension

Esta página aún no está disponible en tu idioma.

An extension is an ordinary native binary. vrOS launches it, supervises it, and talks to it over a local pipe — a named pipe on Windows, a Unix domain socket on Linux — using VXP, a small JSON-RPC 2.0 protocol. One JSON object per line, one pipe per extension, and no network anywhere in the transport.

There is a Rust SDK crate that gives you the manifest schema, the wire types, and an async client. It is a convenience, not a requirement: VXP is plain JSON-RPC over a pipe, and any language that can open one and write a line can speak it. The protocol reference has every request, reply, and event as it appears on the wire.

For the shape of a working extension, read the quickstart.

Out-of-process Bundled
Runs as its own process, spawned and supervised by vrOS a supervised task inside vrOS
Crash isolation full — the process boundary panic isolation and restart
Language any Rust
Who ships it you vrOS

The extensions that come with vrOS — VRChat OSC, the soundboard — are bundled, and they are built on the same API you get. If a first-party feature cannot be built on the extension API, the API is wrong. Both tiers go through the same dispatch path, so the semantics are identical, and moving an extension from bundled to its own process is a manifest change and nothing else.

Your manifest, vros-ext.toml, declares what you need. Nothing is granted by default. An installed extension appears in Settings → Extensions as disabled, with its requested capabilities listed, and stays stopped until the user decides. A version bump asking for something new re-asks for that delta only.

Capability What the user is agreeing to
bus Receive and publish the event topics it lists
overlays Create new panels at runtime, beyond the ones the manifest declares
audio Load sounds and play them through the vrOS audio engine
media Play video and audio from the internet in a vrOS player; audio can continue while the video is hidden
face_provide Offer a face-tracking source (eyes, mouth, or both)
face_read Read the face-tracking data the user has selected
secrets Store credentials in the OS secret store
capture Request capture overlays of specific windows
osc Open OSC sockets of its own
network Open network connections of its own

Declaring none of them still gives you the handshake, your own KV store, your own bus prefix, your manifest’s panels, commands, settings, strings, and panel images.

The two face capabilities are granted on their own. Consenting to an extension leaves them off; the user turns them on per extension, and can turn them off again while it runs.

capture is in the manifest schema, but no capture namespace is served yet. The host refuses what it does not serve, so asking early gets you a typed error rather than silence.

Secrets are backed by Credential Manager on Windows. On Linux there is no credential backend wired up yet, so the store refuses to persist rather than pretending — plan on asking per session there.

Namespace Needs What it does
hello — The mandatory first frame. The reply echoes the granted capability set, the user’s locale, and their vrOS content folder.
kv — A per-extension key/value store that survives restarts, crashes, and reboots.
secrets secrets Named slots in the OS credential store, never written to disk by vrOS.
bus — Publish and subscribe. You may always use your own ext.<id>.* prefix; anything else needs a pattern in your manifest. vr.lifecycle is the core topic relayed today.
panel — Push properties into your panels and receive their callbacks. Defining a new panel at runtime needs overlays.
assets — Upload images, still or animated, that your panels display.
commands — Register named commands the user can bind and trigger.
settings — Read the values from a form vrOS renders out of your manifest.
strings — Locale-resolved lookup for panel text.
audio audio Load sound files, then play them on the soundboard, mic-feed, or UI channels.
media media Open video or audio streams in a vrOS player and control them — four at a time across all of vrOS. Windows only today.
face face_provide / face_read Register a face-tracking source and publish samples, or subscribe to the selected eyes and mouth data.

Panels are .slint source files that ship in your extension folder. The host compiles them in a fenced interpreter, so no foreign code runs inside vrOS, and a panel that fails to compile disables itself with a visible badge instead of taking anything down with it. The host renders them with the same engine it uses for its own screens, so your panel picks up the user’s theme, scale, and density automatically.

Two roles are declared in [panels]: dash-tab puts a tab on the Dash rail and on the desktop app, and overlay gives you a floating panel the user can place, anchor to a wrist, and get back in the same spot after a restart. An overlay can suggest its physical width in the world with width_mm, from 100 to 10000; the default is 400, and the user’s own resize wins once they make one. The rail slot can carry a label of up to four characters (rail_label) and one of the icons vrOS draws itself (icon).

Five rules shape how you write one:

  • The only import allowed is @vros/theme.slint. No other imports, no image files, no @tr.
  • No @tr means words arrive as properties. Declare string properties and push the text from your extension.
  • Images arrive the same way. Upload decoded pixels through assets, then push their keys into an [image] property. vrOS never fetches a URL for you.
  • Push properties only while the panel is shown. The host sends panel_shown and panel_hidden edges, coalesces what you push to about 30 updates a second, and rate-limits extensions that push regardless.
  • The manifest may suggest a panel size. The host clamps it.

A panel with text fields opts into the VR keyboard with two reserved members on its root component: fire callback field-focus(bool) on each field’s focus edge, and clear your fields’ focus when the host bumps in property <int> field-blur. A panel that declares neither simply receives no keys. The protocol reference has the full contract.

vrOS launches your binary with the endpoint in the environment. Send hello within five seconds of launch, then initialize — anything sent before a valid hello is refused. Your restart policy is never, on-crash, or always; a crash restarts you with backoff, and an extension that keeps crashing lands at disabled-with-reason in the Extensions pane rather than being quietly dropped. A clean exit with code 0 is not restarted.

Treat KV as your only durable state and rebuild everything else on start. vrOS may restart you at any time, and the point of the store is that a restart reads as a resume.

run_without_vr defaults to true, so you keep running with no headset and no VR runtime up. Extensions that care subscribe to vr.lifecycle; VR going away hides your overlays and never tears down your state.

Idle should mean idle. vrOS holds itself to zero work per frame when nothing has changed and expects the same of extensions: block on your connection and your own sockets instead of polling, and keep work inside the window between shown and hidden. A frame is capped at 3 MiB — room for the largest legal animated image — and the host answers over-cap or hostile requests with typed errors rather than dropping you.

The protocol version is MAJOR.MINOR, and the current one is VXP 1.7. The host accepts the current major and the one before it, and minors only add: new fields, new requests, new events. An extension written against an older 1.x keeps working on a newer host; it just does not see what was added after it. A few events are sent only to extensions that said hello with a new enough minor, so an old client never receives a shape it cannot read. The protocol reference lists what each minor added.

The SDK crate, the reference hello extension, the theme module, and the authoring contract all exist and are licensed MIT or Apache-2.0 at your option, but they are not published yet — the crate is not on crates.io and the repository is private. This page and the quickstart will link them when that changes. Until then, ask in Discord.