This Friday: Hear from Sanity + Vercel experts on AI and better personalization in e-commerce

Sanity Google Analytics Dashboard

Sanity Studio plugin that adds Google Analytics 4 and Google Search Console dashboard tools to your studio

By Desai Hardik

Install command

npm i sanity-plugin-ga-dashboard

sanity-plugin-ga-dashboard

A Sanity Studio plugin that embeds a full Google Analytics 4 dashboard directly into your studio. View all your GA4 metrics without ever leaving Sanity.

npm version License: MIT


Table of Contents


Features

  • 7 dashboard tabs — Overview, Traffic, Content, Audience, Geography, Events, and Acquisition
  • Real-time active users — live counter refreshed every 30 seconds
  • Date range picker — 7, 14, 30, and 90-day windows
  • 16 parallel GA4 Data API queries — fast, simultaneous data loading
  • Error-resilient — uses Promise.allSettled() so a single failed query never crashes the dashboard
  • Built-in Next.js API handler — one-line route setup via sanity-plugin-ga-dashboard/api
  • JWT service-account auth — powered by jose, with in-memory token caching (1-hour lifetime)
  • HTTP response caching — 5-minute public cache + 60-second stale-while-revalidate
  • Recharts visualisations — area charts, bar charts, pie charts, and horizontal bar charts
  • Dark/light mode — respects Sanity Studio's colour scheme automatically
  • Fully typed — all data structures exported as TypeScript interfaces
  • Node ≥ 18 and Sanity ≥ 3 compatible

How It Works

Sanity Studio (browser)
  └── googleAnalyticsPlugin
        └── <Dashboard apiUrl="/api/analytics" />
              └── fetch /api/analytics?range=30
                    └── Your Next.js API route
                          └── GET handler (sanity-plugin-ga-dashboard/api)
                                ├── Authenticates with Google via JWT service account
                                └── Runs 16 parallel GA4 Data API reports

The plugin itself runs entirely in the browser. It calls a proxy API route that you add to your Next.js app. That route authenticates server-side with a Google service account — keeping your private key off the client.


Requirements

DependencyVersion
Node.js≥ 18
React≥ 18
Sanity≥ 3
@sanity/ui≥ 2
@sanity/icons≥ 3

Installation

npm install sanity-plugin-ga-dashboard
# or
pnpm add sanity-plugin-ga-dashboard
# or
yarn add sanity-plugin-ga-dashboard

Setup

1. Google Cloud & Service Account

  1. Open Google Cloud Console and create or select a project.
  2. Enable the Google Analytics Data API (analyticsdata.googleapis.com).
  3. Go to IAM & Admin → Service Accounts → Create Service Account.
  4. Give it any name, then click Done.
  5. Open the service account, go to Keys → Add Key → Create new key → JSON, and download the file.
  6. In Google AnalyticsAdmin → Account Access Management, add the service account client_email with the Viewer role.

2. Environment Variables

Add the following to your Next.js app's .env.local. Never expose these in the browser.

# .env.local

# Numeric property ID — found in GA Admin → Property Settings
GA_PROPERTY_ID=123456789

# From the downloaded service account JSON key
GA_SERVICE_ACCOUNT_EMAIL=my-sa@my-project.iam.gserviceaccount.com
GA_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\nMIIE...\n-----END PRIVATE KEY-----\n"

Tip: The GA_PRIVATE_KEY value must preserve the literal \n characters exactly as they appear in the JSON file. Wrap the entire value in double quotes.

3. Add the API Route (Next.js)

Create the file src/app/api/analytics/route.ts in your Next.js app and re-export the built-in handler:

// src/app/api/analytics/route.ts
export { GET } from "sanity-plugin-ga-dashboard/api";

That's it — no boilerplate needed. The handler reads your environment variables, authenticates with Google, fires all 16 GA4 reports in parallel, and returns the structured JSON response.

For a custom API URL or a non-Next.js framework, see API Reference.

4. Register the Plugin

// sanity.config.ts
import { defineConfig } from "sanity";
import { googleAnalyticsPlugin } from "sanity-plugin-ga-dashboard";

export default defineConfig({
  // ... your other config
  plugins: [
    googleAnalyticsPlugin({
      // apiUrl: "/api/analytics", // default — change if your route path differs
      // disabled: false,          // set to true to hide the tool in specific environments
    }),
  ],
});

Once configured, a Google Analytics entry appears in your Sanity Studio sidebar.


Configuration

interface GoogleAnalyticsPluginConfig {
  /**
   * URL of the /api/analytics proxy in your Next.js app.
   * @default "/api/analytics"
   */
  apiUrl?: string;

  /**
   * Disable the plugin entirely.
   * Useful for showing the dashboard only in specific environments.
   * @default false
   */
  disabled?: boolean;
}

Example — production only:

googleAnalyticsPlugin({
  disabled: process.env.NODE_ENV !== "production",
});

Example — custom API route path:

googleAnalyticsPlugin({
  apiUrl: "/api/ga-data",
});

Dashboard Tabs

TabWhat you see
Overview10 KPI cards (users, sessions, page views, bounce rate, engagement rate, etc.) + real-time active users
TrafficDaily users/sessions/page views area chart, hourly traffic bar chart for today
ContentTop 15 pages by page views, top 10 landing pages with bounce rate
AudienceDevice breakdown (desktop/mobile/tablet), new vs. returning visitors, browsers, operating systems
GeographyTop 10 countries and top 10 cities ranked by user count
EventsTop 15 GA4 events ranked by event count, with unique user counts
AcquisitionChannel grouping (Organic, Direct, Social, Email, etc.), traffic sources (source/medium), top 10 referrers

Overview KPI Cards

MetricDescription
Total UsersUnique users in the selected period
New UsersFirst-time visitors
SessionsTotal browsing sessions
Page ViewsTotal pages viewed (including repeated views)
Avg. DurationAverage session duration
Bounce RateSessions with no interaction
Engaged SessionsSessions ≥ 10 s, or with a conversion, or ≥ 2 page views
Engagement RateEngaged sessions / total sessions
Pages / SessionAverage pages viewed per session
Events / SessionAverage events fired per session

Exported Types

Import any type from the main entry point:

import type {
  GoogleAnalyticsPluginConfig,
  AnalyticsData,
  DateRange,
  OverviewMetrics,
  TimeSeriesDataPoint,
  HourlyDataPoint,
  TopPage,
  LandingPage,
  DeviceCategory,
  BrowserData,
  OsData,
  CountryData,
  CityData,
  TrafficSource,
  ChannelData,
  UserTypeData,
  EventData,
  ReferrerData,
} from "sanity-plugin-ga-dashboard";

AnalyticsData

The full response shape returned by the API route and consumed by the dashboard.

interface AnalyticsData {
  activeUsers: number;
  overview: OverviewMetrics;
  timeSeries: TimeSeriesDataPoint[];
  hourlyToday: HourlyDataPoint[];
  topPages: TopPage[];
  landingPages: LandingPage[];
  devices: DeviceCategory[];
  browsers: BrowserData[];
  operatingSystems: OsData[];
  countries: CountryData[];
  cities: CityData[];
  trafficSources: TrafficSource[];
  channels: ChannelData[];
  newVsReturning: UserTypeData[];
  topEvents: EventData[];
  referrers: ReferrerData[];
}

DateRange

type DateRange = "7" | "14" | "30" | "90";

API Reference

GET handler — sanity-plugin-ga-dashboard/api

A ready-made Next.js App Router GET handler.

Query parameters

ParameterTypeDefaultDescription
range"7" | "14" | "30" | "90""30"Number of days to look back

Required environment variables

VariableDescription
GA_PROPERTY_IDNumeric GA4 property ID
GA_SERVICE_ACCOUNT_EMAILclient_email from the service account JSON key
GA_PRIVATE_KEYprivate_key from the service account JSON key

Response shape

{
  "activeUsers": 42,
  "overview": { "totalUsers": 1200, "sessions": 1800, "...": "..." },
  "timeSeries": [{ "date": "2026-03-01", "users": 80, "sessions": 100, "pageViews": 200 }],
  "topPages": [{ "path": "/blog/hello", "pageViews": 500, "users": 400 }],
  "..."
}

Response headers include Cache-Control: public, s-maxage=300, stale-while-revalidate=60.

Using in a non-Next.js framework

import { GET } from "sanity-plugin-ga-dashboard/api";

// Any framework that speaks Web Request/Response (e.g. Remix, Hono, Bun)
export const handler = (req: Request) => GET(req);

Troubleshooting

"Missing GA_SERVICE_ACCOUNT_EMAIL or GA_PRIVATE_KEY env vars" → Ensure the variables are in .env.local (not .env), the Next.js dev server has been restarted, and the key value is wrapped in quotes.

"Failed to obtain access token" → Verify the service account JSON key is valid and has not been revoked. Re-download and update your .env.local if needed.

"GA_PROPERTY_ID not configured" → Add GA_PROPERTY_ID=<your-numeric-id> to .env.local. The ID is a number like 123456789, not the UA- or G- measurement ID.

Dashboard shows "No data" → The service account must have at least Viewer access on the GA4 property. Check GA Admin → Account Access Management.

Private key formatting issues → The private_key field in the JSON file contains literal \n sequences. When pasting into .env.local, wrap the entire value in double quotes and do not expand the \n characters.

# Correct
GA_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\nMIIE...\n-----END PRIVATE KEY-----\n"

Changelog

See CHANGELOG.md.


License

MIT © Hardik Desai

Related contributions