> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ooneex.com/llms.txt
> Use this file to discover all available pages before exploring further.

# PostHog

> Capture product analytics events with PostHog behind the framework's IAnalytics interface.

`PostHogAnalytics` is the [Analytics](/components/analytics) component's [PostHog](https://posthog.com) backend. It wraps the official `posthog-node` client to capture product events from the server, attaching a distinct user id, event properties, and optional groups. It implements the framework's `IAnalytics` interface, so call sites stay provider-agnostic and you can swap the backend without touching the code that emits events.

## Why PostHog

* **Server-side capture.** Send events directly from your backend with `posthog-node` — no client SDK required.
* **Typed capture options.** `capture()` takes a typed payload: distinct id, event name, properties, and optional groups.
* **EU or US hosting.** Defaults to the EU cloud and is configurable for US cloud or a self-hosted instance.
* **Graceful shutdown.** `shutdown()` flushes buffered events before the process exits.
* **Container-managed.** Registered with `@decorator.analytics()` and resolved from the container.

## Installation

`PostHogAnalytics` ships with `@ooneex/analytics` and depends on the PostHog Node client.

```bash theme={null}
bun add @ooneex/analytics posthog-node
```

## Environment variables

| Variable                          | Required | Purpose                                                                            |
| --------------------------------- | -------- | ---------------------------------------------------------------------------------- |
| `ANALYTICS_POSTHOG_PROJECT_TOKEN` | Yes      | PostHog project API key. Missing throws `AnalyticsException` (`API_KEY_REQUIRED`). |
| `ANALYTICS_POSTHOG_HOST`          | No       | PostHog instance host. Defaults to `https://eu.i.posthog.com`.                     |

```bash theme={null}
ANALYTICS_POSTHOG_PROJECT_TOKEN=phc_xxxxxxxxxxxxxxxxxxxxxxxx
# Use the US cloud or a self-hosted instance instead of the EU default:
ANALYTICS_POSTHOG_HOST=https://us.i.posthog.com
```

The project token is validated when `PostHogAnalytics` is constructed, so a missing token fails fast at startup.

## How it works

On construction, the backend creates a single `PostHog` client pointed at your host. Each `capture()` call maps your payload onto a PostHog event: the `id` becomes the `distinctId`, `properties` are sent under `$set`, the current timestamp is attached, and any `groups` are forwarded. Capture is fire-and-forget — events are buffered by the client and flushed in the background.

| Method             | Purpose                                     |
| ------------------ | ------------------------------------------- |
| `capture(options)` | Queue an event for the given distinct id.   |
| `shutdown()`       | Flush buffered events and close the client. |

The capture payload:

| Field        | Type                               | Purpose                                                 |
| ------------ | ---------------------------------- | ------------------------------------------------------- |
| `id`         | `string`                           | Distinct id of the user or entity the event belongs to. |
| `event`      | `string`                           | Event name, e.g. `"order_completed"`.                   |
| `properties` | `Record<string, unknown>`          | Optional event properties; sent as `$set`.              |
| `groups`     | `Record<string, string \| number>` | Optional group analytics keys.                          |

## Usage

```typescript theme={null}
import { container } from "@ooneex/container";
import { PostHogAnalytics } from "@ooneex/analytics";

const analytics = container.get(PostHogAnalytics);

// Capture an event
analytics.capture({
  id: "user_123",
  event: "order_completed",
  properties: {
    orderId: "ord_456",
    total: 4200,
    currency: "EUR",
  },
});

// Group analytics
analytics.capture({
  id: "user_123",
  event: "feature_used",
  properties: { feature: "export" },
  groups: { company: "acme_inc" },
});

// Flush before the process exits
await analytics.shutdown();
```

Inject it into a service to record events as part of your domain logic:

```typescript theme={null}
import { inject } from "@ooneex/container";
import { PostHogAnalytics } from "@ooneex/analytics";

export class CheckoutService {
  constructor(
    @inject(PostHogAnalytics) private readonly analytics: PostHogAnalytics,
  ) {}

  public async completeOrder(userId: string, order: Order) {
    // ... persist the order ...
    this.analytics.capture({
      id: userId,
      event: "order_completed",
      properties: { orderId: order.id, total: order.total },
    });
  }
}
```

## Use in the app

In an `@ooneex/app` application, analytics isn't a dedicated `App` config slot — `PostHogAnalytics` registers itself with the container through `@decorator.analytics()` as soon as the class is imported, and you resolve or inject it wherever you record events.

```bash theme={null}
bun add @ooneex/app @ooneex/analytics posthog-node
```

Inject it into a service or controller and capture events as part of your domain logic:

```typescript theme={null}
import { inject } from "@ooneex/container";
import { PostHogAnalytics } from "@ooneex/analytics";

export class CheckoutService {
  constructor(
    @inject(PostHogAnalytics) private readonly analytics: PostHogAnalytics,
  ) {}

  public async completeOrder(userId: string, order: Order) {
    // ... persist the order ...
    this.analytics.capture({
      id: userId,
      event: "order_completed",
      properties: { orderId: order.id, total: order.total },
    });
  }
}
```

Because capture is buffered, flush the client before the process exits — for example from a shutdown hook or an `onStart` handler that registers one:

```typescript theme={null}
import { container } from "@ooneex/container";
import { PostHogAnalytics } from "@ooneex/analytics";

process.on("SIGTERM", async () => {
  await container.get(PostHogAnalytics).shutdown();
  process.exit(0);
});
```

Set `ANALYTICS_POSTHOG_PROJECT_TOKEN` (and optionally `ANALYTICS_POSTHOG_HOST`) in your `.env.yml` (or environment) so the client can initialize.

## Exceptions

`PostHogAnalytics` throws `AnalyticsException` on misconfiguration, carrying a machine-readable `key`.

| Key                | When                                                                         |
| ------------------ | ---------------------------------------------------------------------------- |
| `API_KEY_REQUIRED` | `PostHogAnalytics` is constructed without `ANALYTICS_POSTHOG_PROJECT_TOKEN`. |

```typescript theme={null}
import { AnalyticsException } from "@ooneex/analytics";

try {
  const analytics = container.get(PostHogAnalytics);
} catch (error) {
  if (error instanceof AnalyticsException && error.key === "API_KEY_REQUIRED") {
    logger.error("PostHog is missing its project token", error.data);
  }
  throw error;
}
```

## Best practices

* **Call `shutdown()` on exit.** Capture is buffered; flush before the process terminates so you don't drop the last events.
* **Use stable distinct ids.** Pass a consistent `id` per user or entity so events stitch together into one timeline.
* **Name events consistently.** Use a convention like `noun_verb` (`order_completed`, `user_signed_up`) so they're easy to query.
* **Keep the project token in the environment.** Load it from `.env`; never hard-code it.
* **Match the host to your data region.** Override `ANALYTICS_POSTHOG_HOST` for the US cloud or a self-hosted instance — the default is the EU cloud.

See the [Analytics component](/components/analytics) for the provider interface and how events flow through the framework.
