---
title: "How to Build a Cross-Platform Analytics Dashboard: Google Analytics, Mixpanel, PostHog"
img: https://s3.us-east-2.amazonaws.com/unified-article-images/how_to_build_a_cross_platform_analytics_dashboard_google_analytics_mixpanel_posthog-icon.png
date: 2026-06-30T14:16:00.000Z
tag: Product
description: "Web and product analytics live in different platforms — Google Analytics for web traffic, Mixpanel and PostHog for product events, YouTube Analytics for video,..."
url: "https://unified.to/blog/how_to_build_a_cross_platform_analytics_dashboard_google_analytics_mixpanel_posthog"
---

# How to Build a Cross-Platform Analytics Dashboard: Google Analytics, Mixpanel, PostHog
------
_June 30, 2026_

Web and product analytics live in different platforms — Google Analytics for web traffic, Mixpanel and PostHog for product events, YouTube Analytics for video, Pendo for in-app adoption. Each has its own API, data model, metric definitions, and reporting structure. If your product surfaces analytics to customers, supporting more than one platform means building and maintaining a separate integration for each.


The [Unified Analytics API](https://unified.to/analytics) normalizes analytics data into one model. Reports, properties, visitors, and events come back in a consistent shape regardless of source, so you build the dashboard once and let customers connect whichever platform they use.


This guide builds a cross-platform analytics dashboard: discover the properties a customer has connected, pull normalized reports with dimensioned metrics, and drill into visitors and events where the platform supports them.


## Start with reports, not raw events


The most important design decision in a cross-platform analytics dashboard is what to build on.


Raw session and event data varies widely in availability across analytics platforms — some expose detailed session records, others only expose aggregated reporting. The one surface that is consistent across every supported platform is the **Report**: aggregated metrics over a date range, broken down by dimension.


A report's `metrics` array is the foundation. Each metric carries:

- **`value`** — the numeric value
- **`type`** — what's being measured (`USERS`, `SESSIONS`, `PAGE_VIEWS`, `CONVERSIONS`, `REVENUE`, `BOUNCE_RATE`, `VIDEO_VIEWS`, and ~25 more)
- **`dimension`** — how it's broken down (`DATE`, `SOURCE`, `COUNTRY`, `DEVICE_TYPE`, `PAGE`, `CAMPAIGN`, and more)
- **`dimension_value`** — the specific value for that breakdown (e.g. `"US"` for a `COUNTRY` dimension)

That structure — value by type by dimension — is what powers every chart in a traffic dashboard, and it's available across Google Analytics, Mixpanel, PostHog, Pendo, YouTube Analytics, and additional platforms.


---


## Step 1: Initialize the SDK


```bash
npm install @unified-api/typescript-sdk
```


```typescript
import { UnifiedTo } from '@unified-api/typescript-sdk';

const sdk = new UnifiedTo({
  security: { jwt: process.env.UNIFIED_API_KEY! },
});

const connectionId = process.env.UNIFIED_CONNECTION_ID!;
```


The `connectionId` identifies the authorized customer account — a connected analytics platform. Embed Unified's authorization component to handle the connection flow without building [OAuth](/embeddedauth) per platform.


---


## Step 2: Discover connected properties


A property is the site, app, or analytics account being measured. Start by listing the properties available on the connection.


```typescript
async function getProperties(connectionId: string) {
  const properties = await sdk.analytics.listAnalyticsProperties({
    connectionId,
    limit: 100,
  });

  return (properties ?? []).map((p: any) => ({
    id: p.id,
    name: p.name,
    timezone: p.timezone ?? null,
    currency: p.currency ?? null,
  }));
}
```


Properties give you the `property_id` values used to scope reports and other queries. `id` and `name` are returned across all supported platforms; `timezone`, `currency`, and `industry` are available where the platform provides them.


---


## Step 3: Pull a report with dimensioned metrics


The report endpoint accepts a property, a date range, and optional `type` and `dimension` filters. This is the core dashboard query.


```typescript
async function getReport(
  connectionId: string,
  propertyId: string,
  startDate: string,
  endDate: string,
  metricType?: string,
  dimension?: string
) {
  const reports = await sdk.analytics.listAnalyticsReports({
    connectionId,
    property_id: propertyId,
    start_gte: startDate,        // ISO-8601
    end_lt: endDate,
    type: metricType ?? '',      // e.g. 'SESSIONS', 'CONVERSIONS', 'REVENUE'
    dimension: dimension ?? '',  // e.g. 'SOURCE', 'COUNTRY', 'DATE'
    limit: 100,
  });

  return reports ?? [];
}
```


The `type` and `dimension` parameters map to the normalized enums. For example, `type: 'SESSIONS'` with `dimension: 'SOURCE'` returns sessions broken down by acquisition source; `type: 'PAGE_VIEWS'` with `dimension: 'COUNTRY'` returns page views by country.


---


## Step 4: Shape report metrics for charts


A report returns a `metrics` array. Each entry is a value tagged with its type, dimension, and dimension value. Transform that into chart-ready series.


```typescript
type MetricPoint = {
  label: string;   // the dimension value, e.g. "google", "US", "2026-01-15"
  value: number;
};

// Extract a single metric type, broken down by its dimension, sorted descending
function metricBreakdown(reports: any[], metricType: string): MetricPoint[] {
  const points: MetricPoint[] = [];

  for (const report of reports) {
    for (const m of report.metrics ?? []) {
      if (m.type === metricType && m.dimension_value != null) {
        points.push({
          label: m.dimension_value,
          value: m.value ?? 0,
        });
      }
    }
  }

  return points.sort((a, b) => b.value - a.value);
}

// Sum a metric type across all entries — for a single top-line number
function metricTotal(reports: any[], metricType: string): number {
  let total = 0;
  for (const report of reports) {
    for (const m of report.metrics ?? []) {
      if (m.type === metricType) total += m.value ?? 0;
    }
  }
  return total;
}
```


---


## Step 5: Build the dashboard views


Compose the report data into the views a traffic dashboard renders — top-line totals, an acquisition breakdown, and a geographic breakdown.


```typescript
async function buildAnalyticsDashboard(
  connectionId: string,
  propertyId: string,
  startDate: string,
  endDate: string
) {
  // Sessions broken down by source — for the acquisition chart
  const sessionsBySource = await getReport(
    connectionId, propertyId, startDate, endDate, 'SESSIONS', 'SOURCE'
  );

  // Page views broken down by country — for the geo map
  const viewsByCountry = await getReport(
    connectionId, propertyId, startDate, endDate, 'PAGE_VIEWS', 'COUNTRY'
  );

  // Sessions over time — for the trend line
  const sessionsOverTime = await getReport(
    connectionId, propertyId, startDate, endDate, 'SESSIONS', 'DATE'
  );

  return {
    totals: {
      sessions: metricTotal(sessionsBySource, 'SESSIONS'),
      pageViews: metricTotal(viewsByCountry, 'PAGE_VIEWS'),
    },
    acquisitionBySource: metricBreakdown(sessionsBySource, 'SESSIONS'),
    viewsByCountry: metricBreakdown(viewsByCountry, 'PAGE_VIEWS'),
    sessionsTrend: metricBreakdown(sessionsOverTime, 'SESSIONS'),
  };
}

// Usage
const dashboard = await buildAnalyticsDashboard(
  connectionId,
  'property_123',
  '2026-01-01T00:00:00Z',
  '2026-06-01T00:00:00Z'
);

console.log(`${dashboard.totals.sessions} sessions`);
console.log(`Top source: ${dashboard.acquisitionBySource[0]?.label}`);
```


Because the report model is normalized, this dashboard code runs the same whether the customer connected Google Analytics, Mixpanel, PostHog, Pendo, or YouTube Analytics. The metric types and dimensions are consistent; the platform differences are handled in the API layer.


---


## Step 6: Drill into visitors and events where supported


For platforms that expose visitor-level and event-level detail, you can drill deeper. Visitor and event availability varies by platform, so treat these as enrichment on top of the report-based dashboard rather than its foundation.


**Visitors** — identity and engagement totals:


```typescript
async function getTopVisitors(connectionId: string, propertyId: string) {
  const visitors = await sdk.analytics.listAnalyticsVisitors({
    connectionId,
    property_id: propertyId,
    limit: 50,
  });

  return (visitors ?? []).map((v: any) => ({
    id: v.id,
    name: v.name ?? null,
    email: v.email ?? null,
    country: v.country ?? null,
    firstSeen: v.first_seen_at,
    lastSeen: v.last_seen_at,
    totalSessions: v.total_sessions ?? 0,
    totalPageViews: v.total_page_views ?? 0,
    totalEvents: v.total_events ?? 0,
  }));
}
```


**Events** — individual tracked actions, filterable by type:


```typescript
async function getConversionEvents(
  connectionId: string,
  propertyId: string,
  startDate: string,
  endDate: string
) {
  const events = await sdk.analytics.listAnalyticsEvents({
    connectionId,
    property_id: propertyId,
    type: 'PURCHASE',          // or SIGN_UP, FORM_SUBMIT, etc.
    start_gte: startDate,
    end_lt: endDate,
    limit: 100,
  });

  return (events ?? []).map((e: any) => ({
    name: e.name,
    type: e.event_type,
    value: e.value ?? null,
    currency: e.currency ?? null,
    source: e.utm_source ?? null,
    campaign: e.utm_campaign ?? null,
    pageUrl: e.page_url ?? null,
  }));
}
```


The `event_type` filter accepts the normalized enum — `PAGE_VIEW`, `PURCHASE`, `SIGN_UP`, `FORM_SUBMIT`, `VIDEO_PLAY`, and others — so conversion and funnel queries use the same vocabulary across platforms. Events with monetary value carry `value` and `currency`, which supports revenue and e-commerce reporting.


---


## Why the unified model matters here


The dashboard code reads normalized values — metric `type` and `dimension` enums, `total_sessions`, `event_type` — not each platform's raw reporting schema. Google Analytics, Mixpanel, PostHog, Pendo, and YouTube Analytics each define metrics and dimensions differently. Unified maps them to one set of enums, so the dashboard logic is written once and works regardless of which platform a customer connects.


When you add another analytics platform, the dashboard doesn't change. Normalization happens in the API layer. Every read is a real-time, pass-through call to the source — Unified does not store the analytics data.


## What Unified handles and what you own


**Unified handles:**

- Authorized connections to Google Analytics, Mixpanel, PostHog, Pendo, YouTube Analytics, and additional platforms
- A normalized analytics model — Property, Visitor, Session, Event, Report — with consistent metric types and dimensions across sources
- Real-time, pass-through reads with no stored data

**You own:**

- The dashboard UI and charting
- Any derived metrics or custom rollups
- The reporting period and refresh logic

→ [Explore the Analytics API documentation](https://docs.unified.to/analytics/overview)


→ [Talk to us about building on the Analytics API](https://unified.to/contact)


---


## Author


Written for Unified.to by [Mallory Greene](https://www.linkedin.com/in/malloryjgreene)


_About the author: Mallory Greene is a writer specializing in generative engine optimization (GEO) and content, through her practice_ [_Search Everywhere_](https://searcheverywhere.ca/)_. She covers integration infrastructure and technical content across Unified.to's technical content library. Based in Toronto._