-
-
Notifications
You must be signed in to change notification settings - Fork 352
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.
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 infoEach 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 listeningThe 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 });
}
});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
});| 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 |
- 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
argsto inspect the full data shape for any action not documented here.