Skip to main content
Upcoming — generated from the latest merged element source; documents unreleased development. Use the channel picker at the top of the sidebar for the docs of a published release.
In development — not yet part of a stable release. Belongs to the Reporting sub-controller in the Ads group. Render <TableElement /> inside <Reporting> (React), or mint the sub and mount off it (vanilla): ads.create('reporting', { … }).create('table', { … }) — 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:

Usage

Props

string
A scoped token. Reading needs ad_campaign:read, ad_group:read, ad:read and stats:read; the row actions additionally need the matching :update and :delete scopes, and duplicating needs :create. Mint it on your server with POST /v1/access_tokens and set a fresh one before it expires. Omitted, the calls carry the viewer’s own session, which only answers same-origin. To have the table pick up the account’s own reporting timezone and currency it additionally needs ad_campaign:create, which is what the preferences endpoint asks for; without it the figures are reported in the viewer’s zone unless you set timezone/currency on the handle.
boolean
What happens when a viewer asks to edit — from a row menu, a draft row, or Edit ad in the details overlay. On by default: the campaign builder opens over the page, on the campaign and step they asked for, so editing works with nothing wired up. Turn it off to handle campaign editing yourself — nothing opens, and the request comes back on editRequested. Defaults to true.
"age" | "gender" | "age_gender" | "placement" | "publisher_platform" | "device_platform" | "impression_device" | "country" | "region" | "hour" | null
How rows are broken down — by country, platform, placement — or null for whole rows. Defaults to null.
string[] | null
The columns on show, by name — standard ones like impressions and roas, and any pixel events the viewer added. Unset, the table opens on its own defaults. Comes back whole on columnsChanged; store the list and pass it here to have the layout stick. Defaults to null.
CustomMetricDefinition[]
Columns computed from the figures already in the table — a formula over them, named and formatted by you. The table can edit them, and every change comes back on customMetricsChanged as the whole new list; persist it and pass it here to have it stick. Defaults to [].
string | null
What the All tab is drilled into, as a source path: null for the roots, ext:* for a platform list, ext:<platform>:* for that platform’s campaigns. Comes back on viewChanged. Defaults to null.
string
What the Ads tab’s search box is filtered to. Defaults to "".
boolean
Whether paused campaigns are listed. Defaults to true.

Direct mode only

These props exist only on the in-process (direct-mode) component — the hosted embed never carries them, and the element renders without them.
direct mode only
Direct mode only. Leave the viewer’s columns and computed metrics to the app around the table — its own store, its own defaults — instead of taking them from columns and customMetrics. An embed cannot set this and should not want to: localStorage inside a frame belongs to the bundle’s origin, so a layout chosen there is invisible to everything else you run.
direct mode only
Direct mode only. Hand the table your router and its links become real navigations — a Link for the row menus, a navigate that honours cmd-click. Left unset, and in every embed, links raise editRequested instead: an element frame has no router, and a plain <a> would move the frame rather than the page around it.

Events

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

onTabChanged

The open tab changed. Mirror it into your own URL if you want it to survive a reload. Signature: ((payload: { tab: "ads" | "all" | "campaigns" | "ad-groups"; }) => void)

onFilterChanged

The viewer narrowed the view — by ticking rows, or by clicking through a campaign to its ad groups. Every surface under the same handle has already followed it. Signature: ((payload: { campaignIds: string[]; adGroupIds: string[]; adIds: string[]; }) => void)

onColumnsChanged

The viewer showed or hid a column. Carries every column now on show, standard and pixel-event alike — store the list and pass it straight back as columns, or the layout resets on the next mount. Signature: ((payload: { columns: string[]; }) => void)

onCustomMetricsChanged

The viewer added, edited or removed a computed column. Carries the WHOLE list either way — store it as given and pass it back as customMetrics, or they are gone on the next mount. Signature: ((payload: { customMetrics: CustomMetricDefinition[]; }) => void)

onViewChanged

The viewer moved one of the table’s own controls — the All tab’s drill, the breakdown, the search box, the paused toggle. Carries all four whichever moved; store them and pass them back as props, or they reset on the next mount. Signature: ((payload: { sourcesDrill: string | null; breakdown: "age" | "gender" | "age_gender" | "placement" | "publisher_platform" | "device_platform" | "impression_device" | "country" | "region" | "hour" | null; query: string; showPausedCampaigns: boolean; }) => void)

onSourcesChanged

What the All tab is now looking at, as source paths ready to hand to a chart: null off the All tab (Whop advertising alone), [] for every source, otherwise the ticked rows or the drilled-into group. Every surface under the same handle has already followed it. Signature: ((payload: { sources: string[] | null; }) => void)

onAttributionModelChanged

The viewer changed which touch gets the credit, from the picker on the All tab. Every surface under the same handle has already re-credited; persist it and pass it back as attributionModel, or it reverts to last touch on the next mount. Signature: ((payload: { attributionModel: "last_touch" | "first_touch"; }) => void)

onLoaderStart

Fired the moment the element’s own loading skeleton has painted inside its frame — 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 — 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 — it re-renders with the merged props. React consumers never call it — updating the JSX props does the same. Signature: (options: Partial<TableElementProps>) => void

Styling

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