Skip to content

Event Subscription API

Map1en edited this page Mar 8, 2026 · 1 revision

VRCX exposes its internal stores via window.$pinia, allowing external programs to subscribe to real-time events without modifying the codebase. This can be used to build webhooks, Discord bots, OBS overlays, or any custom integration.

How It Works

All stores are available as properties on window.$pinia:

window.$pinia.feed; // friend state changes
window.$pinia.friend; // friend add/delete
window.$pinia.notification; // VRChat notifications
window.$pinia.game; // game lifecycle
window.$pinia.location; // self location
window.$pinia.auth; // login/logout
window.$pinia.favorite; // favorites
window.$pinia.instance; // instance info

Each store exposes $onAction() for subscribing to actions. It returns an unsubscribe function:

const unsub = store.$onAction(({ name, args, after, onError }) => {
  // name  — action name (string)
  // args  — arguments passed to the action
  // after — callback after successful completion (receives return value)
});

// call unsub() to stop listening

Example 1: Friend State Changes

The addFeed action on the feed store is the single aggregation point for all friend state changes.

window.$pinia.feed.$onAction(({ name, args }) => {
  if (name !== "addFeed") return;

  const feed = args[0];
  // feed.type — 'Online' | 'Offline' | 'GPS' | 'Status' | 'Avatar' | 'Bio'
  // feed.userId, feed.displayName are always present
  // Other fields vary by type — log the object to inspect

  handleEvent(feed.type, feed); // your logic here
});

Friend add/remove events are separate:

window.$pinia.friend.$onAction(({ name, args }) => {
  if (name === "handleFriendAdd") {
    handleEvent("FriendAdd", { userId: args[0].params.userId });
  }
  if (name === "handleFriendDelete") {
    handleEvent("FriendDelete", { userId: args[0].params.userId });
  }
});

Example 2: VRChat Notifications

window.$pinia.notification.$onAction(({ name, args }) => {
  if (name !== "handlePipelineNotification") return;

  const notification = args[0].json;
  // notification.type — 'friendRequest' | 'invite' | 'requestInvite' | etc.
  // notification.senderUserId, notification.senderUsername, notification.details

  handleEvent(notification.type, notification); // your logic here
});

Action Reference

Store Action args
feed addFeed [0] = feed object ({ type, userId, displayName, ... })
friend handleFriendAdd [0].params.userId
friend handleFriendDelete [0].params.userId
notification handlePipelineNotification [0].json = notification object
notification handleNotification [0].json = notification object
game updateIsGameRunning [0] = isGameRunning, [1] = isSteamVRRunning
game updateIsHmdAfk [0] = isHmdAfk
location setCurrentUserLocation [0] = location, [1] = travelingToLocation
auth loginComplete — (use after() callback)
auth logout
favorite applyFavorite [0] = type, [1] = objectId
favorite handleFavoriteAdd [0].json = favorite object
favorite handleFavoriteDelete [0] = objectId
instance applyInstance [0] = instance JSON; after(ref) = processed instance
instance instanceQueueUpdate [0] = id, [1] = position, [2] = size
user applyUser [0] = user JSON

Notes

  • Subscriptions are non-invasive — they observe without affecting VRCX behavior.
  • Use after() when you need the return value or want to ensure the action succeeded.
  • Subscriptions persist for the page lifetime. Call the returned function to unsubscribe.
  • Log args to inspect the full data shape for any action not documented here.

Clone this wiki locally