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

# PeopleElement

> Everyone the account has seen — visitors and customers resolved from pixel activity, with their source, spend and activity counters. Search, filter by source, event, geography or device, sort any column, and page through. A row click follows `links.person` when the handle configures it, and otherwise comes back to you as `personOpened`.

<Info>**Upcoming.** These docs cover unreleased development, ahead of any published release. Use the channel picker at the top of the sidebar for the docs of a published release.</Info>

*In development, not yet part of a stable release.*

Belongs to the [`Tracking`](/elements/upcoming/tracking/overview) group. Render `<PeopleElement />` inside it (React), or call `tracking.create('people', { … })` on the handle (vanilla). Consumer props and `on<Event>` callbacks both go in the create options / JSX props.

## Preview

A live, interactive demo of this element with sample data:

<div data-whop-demo-shell style={{ position: "relative", minHeight: "320px", transition: "min-height 200ms ease" }}>
  <div data-whop-demo-skeleton style={{ position: "absolute", inset: "0", borderRadius: "12px", background: "rgba(140, 140, 140, 0.12)", pointerEvents: "none", transition: "opacity 200ms ease" }} />

  <div data-whop-demo-native="element:tracking/people" data-whop-elements-version="" style={{ position: "relative" }} />
</div>

## Usage

<div data-whop-usage="tracking/people">
  <CodeGroup>
    ```tsx React theme={null}
    import { WhopElements, Tracking, PeopleElement } from "@whop/elements-react";
    import { loadWhop } from "@whop/elements";

    function Example() {
      return (
        <WhopElements elements={loadWhop()}>
          <Tracking /* options */>
            <PeopleElement onPeriodChanged={(payload) => console.log("periodChanged", payload)} onAttributionModelChanged={(payload) => console.log("attributionModelChanged", payload)} onFiltersChanged={(payload) => console.log("filtersChanged", payload)} onSortChanged={(payload) => console.log("sortChanged", payload)} onPersonOpened={(payload) => console.log("personOpened", payload)} />
          </Tracking>
        </WhopElements>
      );
    }
    ```

    ```html Vanilla theme={null}
    <script src="https://js.whop.cloud/elements/amber/elements.js" data-whop-elements></script>
    <script type="module">
      const tracking = window.WhopElements().tracking.create({ /* options */ });
      tracking.create('people', {
        onPeriodChanged: (payload) => console.log("periodChanged", payload),
        onAttributionModelChanged: (payload) => console.log("attributionModelChanged", payload),
        onFiltersChanged: (payload) => console.log("filtersChanged", payload),
        onSortChanged: (payload) => console.log("sortChanged", payload),
        onPersonOpened: (payload) => console.log("personOpened", payload)
      }).mount('#tracking-people');
    </script>
    ```
  </CodeGroup>
</div>

## Props

<ResponseField name="source" type="string">
  Narrow to people acquired by a traffic source, as a source path — `whop:adcamp_…:*` for one of the account’s campaigns, `ext:*` for ads run elsewhere, `referrer:*`, `direct`. The same value the ads table’s click counts link with, so a page can hydrate this straight from its own URL. Defaults to `""`.
</ResponseField>

<ResponseField name="eventName" type="string">
  Narrow to people who performed an event, by its name — `payment.completed`, `pixel.lead`, `pixel.page`. Defaults to `""`.
</ResponseField>

<ResponseField name="customEvent" type="string">
  Narrow to people who performed a custom pixel event, by its custom name. Pairs with an empty `eventName`. Defaults to `""`.
</ResponseField>

<ResponseField name="utmSource" type="string">
  Narrow by the `utm_source` their traffic carried. Comma-separate to match any of several. Defaults to `""`.
</ResponseField>

<ResponseField name="country" type="string">
  Narrow by country, as a two-letter ISO code — `US`, `GB`. Defaults to `""`.
</ResponseField>

<ResponseField name="device" type="string">
  Narrow by device class — `desktop`, `mobile`, `tablet`, `bot`. Defaults to `""`.
</ResponseField>

<ResponseField name="browser" type="string">
  Narrow by browser name. Defaults to `""`.
</ResponseField>

<ResponseField name="os" type="string">
  Narrow by operating system name. Defaults to `""`.
</ResponseField>

<ResponseField name="hasPurchased" type="string">
  `true` for customers only, `false` for people who never bought, empty for everyone. Defaults to `""`.
</ResponseField>

<ResponseField name="query" type="string">
  What the search box is filtered to — a name, an email, or an identifier. Defaults to `""`.
</ResponseField>

<ResponseField name="audienceId" type="string">
  Narrow to a saved audience (`aud_…`) built from people filters. The audience filter menu moves this. Defaults to `""`.
</ResponseField>

<ResponseField name="eventFrom" type="string">
  Pin the event window for `source`/`eventName` filters to an exact ISO 8601 instant instead of the handle’s `period`. Set both `eventFrom` and `eventTo` or neither. Defaults to `""`.
</ResponseField>

<ResponseField name="eventTo" type="string">
  The pinned event window’s end, as an ISO 8601 instant. Defaults to `""`.
</ResponseField>

<ResponseField name="sort" type="string">
  The sorted column: `name`, `email`, `ltv`, `purchase_count`, `event_count`, `first_seen_at`, `last_seen_at`. Defaults to `"last_seen_at"`.
</ResponseField>

<ResponseField name="dir" type="&#x22;asc&#x22; | &#x22;desc&#x22;">
  The sort direction. Defaults to `"desc"`.
</ResponseField>

## Events

Pass a callback in the create options (or React prop) to receive these.

### `onPeriodChanged`

The viewer moved the window from the period selector. Every surface under the same handle has already followed it; persist it and pass it back as `period`/`customRange` on the handle, or the window resets on the next mount.

**Signature:** `((payload: { period: "today" | "yesterday" | "last_7_days" | "last_14_days" | "last_30_days" | "last_90_days" | "all_time" | "custom"; customRange: { from: string; to: string; } | null; }) => void)`

### `onAttributionModelChanged`

The viewer changed which touch gets the credit, from the filter row’s Attribution menu. Every surface under the same handle has already re-credited; persist it and pass it back as `attributionModel` on the handle, or it reverts on the next mount.

**Signature:** `((payload: { attributionModel: "last_touch" | "first_touch"; }) => void)`

### `onFiltersChanged`

The viewer moved a filter, the search box, or the audience picker. Carries the WHOLE filter state whichever moved — mirror it into your own URL and pass it back as props to make the view reproducible.

**Signature:** `((payload: PeopleFilterBag) => void)`

### `onSortChanged`

The viewer sorted a column. Persist and pass back as `sort`/`dir`, or sorting resets on the next mount.

**Signature:** `((payload: { sort: string; dir: "asc" | "desc"; }) => void)`

### `onPersonOpened`

The viewer clicked a person row and the handle has no `links.person` to follow — open your own person view with it (`personId` is the stable per-person key; `identifier` is the most human-meaningful one the surface has), or pass `links.person` on the handle to turn rows into real anchors instead.

**Signature:** `((payload: { identifier: string; personId: string; }) => void)`

### `onLoaderStart`

Fired the moment the element's own loading skeleton has painted inside its frame. This is the earliest point a consumer-managed loading state can hand off without ever exposing a blank. Always precedes `onReady`; most consumers only need `onReady`.

**Signature:** `(() => void)`

### `onReady`

Fired once the element has booted and painted its first complete frame.

**Signature:** `(() => void)`

### `onError`

Fired when the element fails to load or crashes; the element shows its own error fallback. `message` is human-readable; framework refusals also carry `code` (e.g. `HOST_SOURCE_FAILED`, with `sourceKey` naming the failed hostState key) so hosts can switch on codes, never message text.

**Signature:** `((e: { message: string; code?: string | undefined; sourceKey?: string | undefined; }) => void)`

## Methods

Call these on the element handle, which is the return of `create` (vanilla) or the component `ref` (React).

### `mount`

Place the element on the page: appends its container to `target` (a CSS selector or an element) and starts loading. Nothing renders until this is called. React consumers never call it; the component mounts itself.

**Signature:** `(target: string | HTMLElement) => void`

### `destroy`

Remove the element from the page and release its frame and subscriptions. Safe to call more than once. React consumers never call it; unmounting the component does it.

**Signature:** `() => void`

### `update`

Change this element's consumer props after mount, and it re-renders with the merged props. React consumers never call it; updating the JSX props does the same.

**Signature:** `(options: Partial<PeopleElementProps>) => void`

## Styling

This element exposes no targetable class names. Use `appearance` (theme, accent color, variables) to restyle it.
