Dashboard reference · ~8 minute read · Last updated 2026-04-30

How /dashboard/analytics works — every card mapped to its source pipe

Every metric tied to its source pipe — read this when something on /dashboard/analytics looks off. Each card on the dashboard reads from one or more Tinybird pipes via the server-side /api/pixel/metrics/:pipe proxy. Knowing which pipe feeds which card is the difference between “the dashboard is broken” and “this card legitimately has no data for the selected window.”

How the data flows

The /dashboard/analytics surface is a thin React composition over a set of Tinybird pipes. Every card on the page makes the same shape of call:

  1. The card builds a query string from the active filters: website_id, date_from, date_to, granularity, optional breakdown, optional limit.
  2. The card hits GET /api/pixel/metrics/:pipe?<query> on the same origin (no CORS, session cookie auth, no token in the URL).
  3. The Express route validates the pipe name against PIXEL_PIPE_ALLOWLIST (28 pipes today — any pipe outside the allowlist is rejected before a Tinybird call fires), confirms the website_id belongs to the active workspace (defends against cross-tenant payload leaks), and proxies the call to Tinybird with the workspace’s admin token.
  4. Tinybird runs the pipe SQL and returns { data, meta, rows }. The Express route caches the response in Upstash Redis (300s fresh / 3600s stale-tier fallback) and forwards { data, meta, rows } to the browser.
  5. The card renders data; meta describes the columns, rows is the row count.

For the data architecture upstream of the pipes (Custom Pixel events vs. webhook events vs. polled metrics) see the companion doc /documentation/data/revenue-data-flow. This page covers what happens after the data lands in Tinybird — how each card consumes it.

The page at a glance — card to pipe mapping

Each card on /dashboard/analytics maps to one or more pipes. The active filters (website, date range, granularity) are passed through to every pipe call so the cards stay in sync.

Card Pipe(s) What it counts
HeroCard summary (server route) Sessions, unique visitors, pageviews, bounce rate over the selected window. The cornerstone tile.
AcquisitionCard p_traffic_sources, p_referrers, p_channels, p_campaigns Sessions and visitors broken down by UTM source / medium / campaign or channel inferred from referrer.
GeoCard p_geo Unique visitors per (country, city) pair for the selected window.
GoalsCard p_goal_conversions, p_funnel Auto-tracked __admx_* goals + merchant-defined goals; conversion counts and conversion rate.
PagesCard p_top_pages, p_entry_pages, p_exit_links Top pages by pageview, entry pages (where sessions start), exit links (where visitors leave).
TechCard p_devices, p_tech, p_languages Device class (mobile / tablet / desktop), browser, OS, language preference.
AdvancedSection p_funnel_v2, p_retention_cohort, p_visitor_timeline, p_attributed_revenue, p_ad_ltv Multi-step funnels, retention cohorts, visitor session lists, ad-level revenue + LTV.
SearchConsoleCard p_gsc_keywords Google Search Console keywords, clicks, impressions, average position. Requires GSC connection.

The full pipe allowlist lives in server/routes/pixelMetrics.ts as PIXEL_PIPE_ALLOWLIST. Adding a new card on the dashboard requires adding the pipe to that allowlist; otherwise the proxy returns 400 before the Tinybird call fires (403 is reserved for the IDOR check on website_id).

HeroCard deep dive

HeroCard is the cornerstone of /dashboard/analytics: a four-tile row showing sessions, unique visitors, pageviews, and bounce rate over the selected window, plus a sparkline per tile and a delta vs. the prior period (when Compare mode is on).

Source pipe. HeroCard reads from the server route /api/pixel/analytics/summary, which fans out to a single Tinybird pipe (summary) under the hood. The summary pipe joins visitor, session, and pageview events into one window-aggregated row so HeroCard renders in a single round-trip.

What counts as a session. A session is a contiguous run of pageviews and events from one visitor with no more than 30 minutes of inactivity between events. A 31-minute gap starts a new session. UTM source/medium changes do not split sessions on Admaxxer (they do on GA4) — we attach the UTMs from the entry pageview to the whole session for cleaner attribution.

What counts as a visitor. A visitor is a unique visitor_id cookie issued by the pixel. The cookie is first-party (set on your domain, not on admaxxer.com) and survives 365 days. A visitor returning after the window’s end is not counted in the current window’s visitor total.

How bounce rate is computed. Bounce rate = (sessions with exactly one pageview) ÷ (total sessions). It is a session-level metric, not a visitor-level one. Granularity options (hour, day, week, month) reshape the sparkline; the headline number is always the window total.

AcquisitionCard deep dive

AcquisitionCard answers “where are these visitors coming from?” It splits sessions and visitors by source / medium / campaign, with sub-tabs for channel rollups (Direct, Organic Search, Paid Search, Social, Email, Referral) and per-campaign drilldowns.

Source pipes. Four pipes feed this card: p_traffic_sources (raw UTMs), p_referrers (referer header rollup), p_channels (referrer-inferred channels), p_campaigns (utm_campaign rollup). The card’s sub-tabs map 1:1 onto these pipes.

How UTM-less traffic is bucketed. Sessions arriving without UTMs are bucketed as (direct) / (none) — literal strings, never null. This matches GA4’s convention. If the visitor arrives without UTMs but with a referer header pointing at a known search engine (google.com, bing.com, duckduckgo.com), we re-bucket them under (organic) / (search) in the channel rollup — this is the same heuristic Plausible and Datafast use.

How channels are inferred. The p_channels pipe applies a deterministic referrer-to-channel mapping: google.com, bing.com, duckduckgo.com → Organic Search; facebook.com, instagram.com, tiktok.com, linkedin.com → Social; klaviyo.com, mailto referers, utm_medium=email → Email; everything else → Referral. Paid Search and Paid Social are inferred from utm_medium=cpc or utm_medium=paid_social on top of the referrer mapping. This is why consistent UTM tagging matters — see /documentation/utm-best-practices.

GeoCard deep dive

GeoCard shows where your visitors are located, broken down by country (default tab) and city (drill-in tab). Each row shows unique visitor count, sessions, and a percentage share of the total.

Source pipe. p_geo. The pipe groups by (country, city) for the selected window and counts distinct visitor_id.

How country is determined. The pixel resolves country and city via Tinybird’s built-in MaxMind GeoIP lookup at ingest time — the visitor’s IP is converted to a country code (ISO 3166-1 alpha-2) and a city name before the event is persisted. The IP itself is never stored beyond the GeoIP lookup, in line with the privacy policy.

IP → country fallback. When the GeoIP lookup fails (private-range IP, brand-new MaxMind region, or a Tor exit node), the country is recorded as the literal string (unknown). These rows roll up into a single “Unknown” row in the card — we don’t hide them, because hiding makes the percentage shares add up to less than 100% which confuses operators. If the GeoCard’s top row is (unknown), that’s a signal worth investigating: usually a server-side mirror writing events with a public-egress IP that doesn’t carry the visitor’s real IP.

GoalsCard deep dive

GoalsCard tracks conversions — events the merchant cares about, like checkout completion, signup, or a specific button click. Each row shows the goal name, conversion count, conversion rate, and a sparkline.

Source pipes. p_goal_conversions for the headline counts and p_funnel for the conversion-rate denominator (sessions in the window).

Auto-tracked vs. merchant-defined goals. Admaxxer ships with a Plus bundle of reserved auto-goals namespaced __admx_*: __admx_checkout_completed, __admx_signup, __admx_email_capture, __admx_add_to_cart. These fire automatically when the pixel detects the matching surface (e.g., the Shopify Custom Pixel’s checkout_completed event maps to __admx_checkout_completed). Merchants cannot create or rename a goal in the __admx_* namespace — the prefix is reserved so the auto-bundle stays consistent.

Merchant-defined goals. Any custom track('goal_name') call from your storefront produces a goal row in this card automatically. There’s no “create goal” UI — firing the event once is enough; the goal appears on the next dashboard refresh. Naming convention: snake_case, no __admx_ prefix. Configure goals from the storefront code, not from the dashboard.

PagesCard deep dive

PagesCard surfaces the pages that drive your traffic: top pages by pageview count, entry pages (where a session starts), and exit links (where the visitor leaves the site). Three sub-tabs map onto three pipes.

Source pipes. p_top_pages, p_entry_pages, p_exit_links. All three group by normalized path; the difference is what they count (any pageview vs. only entry pageviews vs. outbound clicks to off-site URLs).

Path normalization rules. Three transforms run in order at ingest time so the dashboard rows aggregate cleanly:

If you’re seeing surprising row splits, the cause is almost always one of these three transforms not matching what your storefront emits. Inspect the raw url field in p_top_pages via an admin curl (see Debug section) to confirm.

TechCard deep dive

TechCard tells you what your visitors are using to reach the site: device class, browser, OS, and language. It’s the smallest card on the dashboard and almost always the first to populate.

Source pipes. p_devices (mobile / tablet / desktop), p_tech (browser + OS), p_languages (Accept-Language header rollup).

User-Agent parsing fidelity. The pixel parses User-Agent server-side at ingest time using the ua-parser-js library (the same parser Plausible and Datafast use). Recent UAs (Chrome 120+, Safari 17+, Firefox 121+) parse cleanly. Legacy UAs and bot UAs that try to look like real browsers may misclassify — the dashboard surfaces an “Other” bucket for unparsed UAs rather than dropping them. If your “Other” row is >5%, check for ad-blockers or privacy extensions stripping the UA header (rare but possible). Apple’s “Hide my IP” relay does NOT affect UA parsing — only the IP-based GeoCard.

SearchConsoleCard deep dive

SearchConsoleCard shows organic search performance: keywords your site ranks for, clicks per keyword, impressions, click-through rate, and average position. It’s the only card that depends on an external connection beyond the pixel.

Source pipe. p_gsc_keywords. The pipe reads from the gsc_metrics Tinybird datasource, which a worker populates daily by hitting the Google Search Console API.

How to connect GSC. Settings › Integrations › Google Search Console › Connect. The OAuth flow asks for the webmasters.readonly scope — read-only access to your verified properties. Once connected, the worker fetches the prior day’s metrics for every property at 04:00 UTC and writes them to gsc_metrics.

24-48h GSC ingestion lag. Google Search Console reports its own data with a 24 to 48 hour lag — this is a Google-side limitation, not an Admaxxer one. So if you connected GSC at 14:00 UTC today and you select “today” on the dashboard, SearchConsoleCard will show zero. Yesterday’s data lands at the next 04:00 UTC tick. The day-before-yesterday is the first “real” row you can rely on.

When a card is empty — common causes

An empty card is rarely a dashboard bug. The four causes below cover >95% of empty-card support tickets:

  1. No pixel installed yet. Until the Admaxxer pixel fires its first pageview event, every card except SearchConsoleCard renders zero. Open /integrations and walk through the Pixel install for your platform — or see the canonical install guide at /documentation/install. The 20 platform-specific guides cover Shopify, WordPress, Webflow, Next.js, GTM, Wix, Squarespace, Magento, BigCommerce, and the universal <script> snippet.
  2. Pixel installed but no traffic in the selected window. The default window is “last 7 days.” A pixel installed today on a low-traffic site might have one or two pageviews in 7 days — not zero, but close enough to look empty. Extend the window via the date picker, or open the FilterToolbar’s “Last 30 days” preset. If you’re testing the install, fire a few pageviews from your own browser and refresh.
  3. Tinybird upstream slow — soft “data refreshing” banner. When Tinybird’s query layer is under load, the proxy retries once on a 502/503/504 (with a 500ms backoff). If the retry fails, the proxy serves stale-tier cache (up to 1 hour old) and the dashboard renders that stale data with a soft “data refreshing” banner. This is rare but real — usually a cold-cache window after a Tinybird datasource truncate-replace, or a regional Tinybird incident. The banner clears on the next successful fetch.
  4. Goal not configured (GoalsCard only). If GoalsCard is the only empty card while everything else has data, your storefront isn’t firing any goal events yet. The four __admx_* auto-goals fire only when the matching surface is detected (e.g., the Shopify Custom Pixel’s checkout_completed event); merchant-defined goals require a track('goal_name') call from your storefront. Add at least one custom goal call to your checkout success page or signup form, fire it once, and the goal appears on the next dashboard refresh.

Performance and caching

Three caching layers keep the dashboard fast without blowing up Tinybird’s query budget:

Real-time pipes refetch every 10 seconds. Two pipes are exempt from the 60s staleTime because they’re explicitly real-time: p_realtime_visitors (the LiveVisitors widget on the header strip) and the AwaitingEventToast poll. These hit the proxy every 10 seconds, but the proxy’s 300s server cache still applies — so the worst case is 6 redundant browser fetches sharing one Tinybird call.

Debug your workspace

Two debug paths exist depending on your access level. Merchant-side: open the FilterToolbar’s “Refresh” button to force a re-fetch (bypasses the 60s browser cache). Admin-side: open the prod container and curl the pipes directly.

Merchant-side

Admin-side

Admins can curl the pipes directly via the prod container. Replace WORKSPACE_ID and WEBSITE_ID with the values from SELECT id, website_id FROM pixel_websites WHERE workspace_id = '...' on Neon. Date format is ISO YYYY-MM-DD.

# SSH into the prod container
ssh admaxxer@5.78.179.73
sudo docker ps --filter name=p5uwddio
sudo docker exec -it <container> bash

# Inside the container, hit the proxy directly with admin auth
curl -s "http://localhost:3000/api/pixel/metrics/p_geo?\
  website_id=admx_YOUR_ID&\
  workspace_id=WORKSPACE_UUID&\
  date_from=2026-04-23&\
  date_to=2026-04-29" | jq '.data | length'

For the data architecture upstream of the pipes (Custom Pixel events vs. webhook events vs. polled metrics), see the companion doc /documentation/data/revenue-data-flow. That page documents the four ingestion paths into Tinybird; this page documents what the dashboard does with them.