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-dashboardsanity-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.
Table of Contents
- Features
- How It Works
- Requirements
- Installation
- Setup
- Configuration
- Dashboard Tabs
- Exported Types
- API Reference
- Troubleshooting
- Changelog
- License
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 reportsThe 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
| Dependency | Version |
|---|---|
| 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-dashboardSetup
1. Google Cloud & Service Account
- Open Google Cloud Console and create or select a project.
- Enable the Google Analytics Data API (
analyticsdata.googleapis.com). - Go to IAM & Admin → Service Accounts → Create Service Account.
- Give it any name, then click Done.
- Open the service account, go to Keys → Add Key → Create new key → JSON, and download the file.
- In Google Analytics → Admin → Account Access Management, add the service account
client_emailwith 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_KEYvalue must preserve the literal\ncharacters 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
| Tab | What you see |
|---|---|
| Overview | 10 KPI cards (users, sessions, page views, bounce rate, engagement rate, etc.) + real-time active users |
| Traffic | Daily users/sessions/page views area chart, hourly traffic bar chart for today |
| Content | Top 15 pages by page views, top 10 landing pages with bounce rate |
| Audience | Device breakdown (desktop/mobile/tablet), new vs. returning visitors, browsers, operating systems |
| Geography | Top 10 countries and top 10 cities ranked by user count |
| Events | Top 15 GA4 events ranked by event count, with unique user counts |
| Acquisition | Channel grouping (Organic, Direct, Social, Email, etc.), traffic sources (source/medium), top 10 referrers |
Overview KPI Cards
| Metric | Description |
|---|---|
| Total Users | Unique users in the selected period |
| New Users | First-time visitors |
| Sessions | Total browsing sessions |
| Page Views | Total pages viewed (including repeated views) |
| Avg. Duration | Average session duration |
| Bounce Rate | Sessions with no interaction |
| Engaged Sessions | Sessions ≥ 10 s, or with a conversion, or ≥ 2 page views |
| Engagement Rate | Engaged sessions / total sessions |
| Pages / Session | Average pages viewed per session |
| Events / Session | Average 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
| Parameter | Type | Default | Description |
|---|---|---|---|
range | "7" | "14" | "30" | "90" | "30" | Number of days to look back |
Required environment variables
| Variable | Description |
|---|---|
GA_PROPERTY_ID | Numeric GA4 property ID |
GA_SERVICE_ACCOUNT_EMAIL | client_email from the service account JSON key |
GA_PRIVATE_KEY | private_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