VSTDOCSv0.0.17

DOCS / AUDIO & NATIVE

PostHog analytics

Know what your users actually do with your plugin — which knobs they touch, which panels they open, which presets they load. @vsreact/posthog captures from the React side and the native PostHogBridge delivers over HTTPS (QuickJS has no network; C++ owns delivery, off the message thread).

Install

bun add @vsreact/posthog

C++ wiring

Construct a PostHogBridge with your project API key and chain it in onNativeCall, exactly like the ParameterBridge. The key lives in C++ — the JS bundle never sees it.

PluginEditor.cpp
vsreact::PostHogBridge::Options analytics;
analytics.apiKey = "phc_...";                             // your project key
analytics.host = "https://eu.i.posthog.com";              // or us / self-hosted
analytics.stateFile = appDataDir.getChildFile ("ph-id");  // persistent anonymous id

posthog = std::make_unique<vsreact::PostHogBridge> (analytics);

options.onNativeCall = [this] (const juce::String& name, const juce::var& args) -> juce::var
{
    if (auto handled = posthog->handleNativeCall (name, args)) return *handled;
    if (auto handled = bridge.handleNativeCall (name, args))   return *handled;
    return {};
};
  • Batches post to {host}/batch/ on a background thread — never the audio or message thread.
  • stateFile persists the anonymous distinct id across sessions; leave it empty for a fresh id per instance.

Capture from React

ui/src/main.tsx
import { render, GenericEditor } from "@vsreact/core";
import { posthog, usePostHogParameters, useCaptureOnMount } from "@vsreact/posthog";

posthog.init({ defaultProperties: { plugin_version: "1.2.0" } });

function App() {
  usePostHogParameters();          // every knob tweak, debounced per parameter
  useCaptureOnMount("plugin_opened");

  return <GenericEditor />;
}

render(<App />);

usePostHogParameters() is the flagship: it subscribes to every host parameter change and captures one parameter_changed event per parameter after the user settles (debounced, default 800ms) — usage analytics for your whole control surface in one line.

The client API

TSX
posthog.capture("preset_loaded", { preset: "Warm Tape" });
posthog.register({ daw: hostName });          // stamped on every event
posthog.registerOnce({ first_seen: today });   // only where unset
posthog.identify("user-123", { plan: "pro" }); // tie to a known user
posthog.alias("licence-XYZ");                  // link another id
posthog.set({ favourite_mode: "TUBE" });       // person properties
posthog.setOnce({ first_version: "1.2.0" });   // only if unset
posthog.group("studio", "abbey-road");         // group analytics
posthog.time("preset_load");                   // start a stopwatch…
posthog.timeEnd("preset_load");                // …capture { duration_ms }
posthog.debug(true);                           // log captures (dev builds)
posthog.flush();                               // force-send now
posthog.reset();                               // fresh anonymous identity
scrub before anything queues
posthog.init({
  beforeSend: (event) => {
    if (event.event === "internal_debug") return null;   // veto
    delete event.properties.project_path;                // scrub
    return event;
  },
});
  • Events queue in JS and flush at 10 events or 10 seconds (tune with init({flushAt, flushIntervalMs})); the queue is capped at maxQueueSize (drop-oldest, default 1000).
  • Every event carries distinct_id, $session_id, and lib metadata automatically.
  • init({sampleRate}) keeps a fraction of sessions and stamps $sample_rate on kept events so PostHog can weight counts.

Error tracking & sessions

ui/src/main.tsx
import { PostHogErrorBoundary, useEditorSession } from "@vsreact/posthog";

function App() {
  useEditorSession();   // editor_session_start / _end { duration_ms }
  return <MainPanel />;
}

render(
  <PostHogErrorBoundary fallback={<Text>Something broke — reopen the window.</Text>}>
    <App />
  </PostHogErrorBoundary>,
);
  • posthog.captureException(error, props?) — a properly-shaped $exception event for PostHog error tracking; the boundary calls it for every render crash and flushes immediately.
  • useCaptureOnUnmount(event) — the closing bookend to useCaptureOnMount, stamped with duration_ms.
  • useScreen(name) / posthog.screen(name) — panel views as $screen events for PostHog screen analytics; posthog.shutdown() flushes and goes silent at editor teardown; init({propertyDenylist}) strips sensitive keys mechanically.
  • posthog.optOut() / optIn() / optedOut — the consent switch: opting out drops new events and discards the unsent queue; init({optOut}) starts disabled. unregister(key) and getSessionId() round out the client.
Respect your users: ship analytics behind a consent toggle in your settings panel, and say what you collect. A <ParamToggle> wired to a "share usage data" flag that drives posthog.optOut() / optIn() is the pattern.

Full messaging model in Native messaging; the bridge type in the C++ API.