Skip to main content
RapidDev - Software Development Agency
bolt-ai-integrationsBolt Chat + API Route

How to Integrate Bolt.new with Google Data Studio (Looker Studio)

Google Data Studio was renamed to Looker Studio in October 2022. Embed Looker Studio reports in Bolt apps using iframe embeds — no API key required, works in the WebContainer preview. For custom analytics dashboards, use the Google Analytics Data API as the programmatic alternative. Configure report sharing for public or link-based access, then use the embed URL in a React iframe component.

What you'll learn

  • How to embed a Looker Studio report in a Bolt app using an iframe component
  • How to configure Looker Studio report sharing for public or link-based embed access
  • How to use the Google Analytics Data API (GA4) as a programmatic alternative to Looker Studio
  • How to authenticate GA4 API requests with a service account in a Next.js API route
  • How to build custom analytics dashboards with real Google Analytics data
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Intermediate18 min read20 minutesOtherApril 2026RapidDev Engineering Team
TL;DR

Google Data Studio was renamed to Looker Studio in October 2022. Embed Looker Studio reports in Bolt apps using iframe embeds — no API key required, works in the WebContainer preview. For custom analytics dashboards, use the Google Analytics Data API as the programmatic alternative. Configure report sharing for public or link-based access, then use the embed URL in a React iframe component.

Embed Looker Studio Reports or Build Custom Analytics in Bolt.new

Google Data Studio was rebranded as Looker Studio in October 2022, but the product and its embed capabilities remain the same. Looker Studio is a free business intelligence tool from Google that connects to dozens of data sources — Google Analytics, Google Ads, BigQuery, Google Sheets, and third-party services — and displays them as interactive charts, tables, and scorecards in shareable reports.

For Bolt developers, there are two integration approaches with different complexity trade-offs. The first is iframe embedding: every Looker Studio report has an embed URL that can be placed inside an HTML iframe. When sharing settings allow it, the report renders inside the iframe with full interactivity — date range pickers, filter dropdowns, and cross-chart filtering all function within the embed. This approach requires no API, no credentials, and no backend code. It works in Bolt's WebContainer preview and in production. The main limitation is styling: you cannot customize the report's visual design from your application — the report looks like Looker Studio, not your brand.

The second approach is building custom dashboards using the Google Analytics Data API (GA4). Instead of embedding a Looker Studio report, you fetch raw metrics from Google Analytics and render them with your own charts, matching your application's design system exactly. This gives complete visual control and lets you combine GA data with other data sources in ways Looker Studio's connectors cannot. The trade-off is authentication complexity: GA4 API requires a Google Cloud service account or OAuth, server-side API routes, and more development work.

Integration method

Bolt Chat + API Route

Looker Studio integration uses two approaches. The simpler path embeds existing Looker Studio reports via iframe using the report's embed URL — this requires no API, no credentials, and works directly in Bolt's WebContainer preview. The more powerful path uses the Google Analytics Data API (GA4) to fetch raw analytics data and build custom visualizations in React using Recharts or similar. The GA4 API requires a service account or OAuth and API routes for server-side access.

Prerequisites

  • A Bolt.new account with a Next.js project
  • For embedding: a Looker Studio report with sharing enabled (View access for anyone with the link)
  • For GA4 API: a Google Analytics 4 property with existing data
  • For GA4 API: a Google Cloud project with Google Analytics Data API enabled
  • For GA4 API: a Google Cloud service account with Viewer role in your GA4 property

Step-by-step guide

1

Embed a Looker Studio Report via Iframe

The simplest Looker Studio integration requires no API calls, no credentials, and no backend code. Every Looker Studio report can be embedded via iframe if sharing is configured correctly. To get the embed URL, open your Looker Studio report and click the Share button in the top-right corner. In the sharing dialog, click 'Schedule email delivery' — no, that is not right. Instead, click the three-dot menu (⋮) in the top right and select 'Embed report.' This opens the embed code dialog showing the full iframe HTML code. The embed URL follows the format: `https://lookerstudio.google.com/embed/reporting/{reportId}/page/{pageId}`. For the embed to work for anyone (not just Google account holders with explicit access), the report must be shared with view access. In the Share dialog, change access to 'Anyone with the link can view.' Without this setting, viewers who are not explicitly added to the report will see an access error inside the iframe. The iframe dimensions in Looker Studio's generated embed code default to 600x450 pixels — adjust these to fit your layout. The report is responsive within those dimensions. Using a percentage-based width (100%) with a fixed height or an aspect-ratio CSS property gives better responsiveness. URL parameters let you pre-filter the report or set date ranges. Append `params={%22df80%22:%22include%25EE%2580%2580IN%25EE%2580%8010%22}` style parameters for built-in filters, or configure your Looker Studio report to accept URL parameters using the 'URL Parameters' data source feature. For date range control, the `df_date_range` parameter sets the report's date range. Security note: embedded Looker Studio reports are only as secure as your sharing settings. If the report contains sensitive business data, use explicit user access rather than link-based sharing, and consider whether embedding is appropriate for your use case.

Bolt.new Prompt

Create a LookerStudioEmbed React component that accepts props: reportUrl (string), title (string, optional), height (number, default 600). The component should render a responsive iframe with the reportUrl, a loading skeleton while the iframe loads, and an onLoad handler that hides the skeleton. Add a fullscreen toggle button that opens the report URL in a new tab. The iframe should have allowFullScreen and sandbox attributes. Show an error state if the iframe fails to load with a link to open the report directly. No API key needed.

Paste this in Bolt.new chat

components/LookerStudioEmbed.tsx
1// components/LookerStudioEmbed.tsx
2'use client';
3import { useState } from 'react';
4
5interface LookerStudioEmbedProps {
6 reportUrl: string;
7 title?: string;
8 height?: number;
9 className?: string;
10}
11
12export function LookerStudioEmbed({
13 reportUrl,
14 title = 'Analytics Report',
15 height = 600,
16 className = '',
17}: LookerStudioEmbedProps) {
18 const [loading, setLoading] = useState(true);
19 const [error, setError] = useState(false);
20
21 // Ensure the URL uses the embed format
22 const embedUrl = reportUrl.includes('/embed/')
23 ? reportUrl
24 : reportUrl.replace('/reporting/', '/embed/reporting/');
25
26 const handleLoad = () => setLoading(false);
27 const handleError = () => {
28 setLoading(false);
29 setError(true);
30 };
31
32 return (
33 <div className={`relative w-full rounded-lg overflow-hidden border border-gray-200 ${className}`}>
34 {/* Header */}
35 <div className="flex items-center justify-between px-4 py-3 bg-white border-b border-gray-200">
36 <h3 className="text-sm font-medium text-gray-700">{title}</h3>
37 <a
38 href={reportUrl}
39 target="_blank"
40 rel="noreferrer"
41 className="text-xs text-blue-600 hover:text-blue-800 flex items-center gap-1"
42 >
43 Open in Looker Studio
44 </a>
45 </div>
46
47 {/* Loading skeleton */}
48 {loading && !error && (
49 <div
50 className="absolute inset-0 top-10 bg-gray-50 flex items-center justify-center"
51 style={{ height }}
52 >
53 <div className="flex flex-col items-center gap-3">
54 <div className="w-8 h-8 border-2 border-blue-600 border-t-transparent rounded-full animate-spin" />
55 <span className="text-sm text-gray-500">Loading report...</span>
56 </div>
57 </div>
58 )}
59
60 {/* Error state */}
61 {error && (
62 <div
63 className="flex flex-col items-center justify-center bg-gray-50 gap-3"
64 style={{ height }}
65 >
66 <p className="text-sm text-gray-600">Report could not load.</p>
67 <p className="text-xs text-gray-500">Ensure the report is shared as &quot;Anyone with the link can view&quot;</p>
68 <a
69 href={reportUrl}
70 target="_blank"
71 rel="noreferrer"
72 className="text-sm text-blue-600 hover:underline"
73 >
74 Open report directly
75 </a>
76 </div>
77 )}
78
79 {/* Iframe */}
80 {!error && (
81 <iframe
82 src={embedUrl}
83 style={{ height, display: loading ? 'none' : 'block' }}
84 className="w-full"
85 title={title}
86 allowFullScreen
87 onLoad={handleLoad}
88 onError={handleError}
89 sandbox="allow-same-origin allow-scripts allow-popups allow-forms"
90 />
91 )}
92 </div>
93 );
94}

Pro tip: The Looker Studio embed URL uses the /embed/ path, not the /reporting/ path from the browser URL bar. If you use the wrong URL format, the embed shows an error. The component above automatically converts /reporting/ to /embed/reporting/ if needed.

Expected result: The LookerStudioEmbed component renders your Looker Studio report inside an iframe. The loading skeleton shows while the report loads and disappears when the content renders. The 'Open in Looker Studio' link opens the full report in a new tab.

2

Set Up Google Analytics Data API Authentication

For building custom dashboards that bypass Looker Studio entirely and fetch raw GA4 data, you need the Google Analytics Data API (v1). Authentication uses a Google Cloud service account — a server identity that can be granted viewer access to your GA4 property without requiring user login. Step 1 — Enable the API: Go to console.cloud.google.com. Create a new project or select an existing one. Search for 'Google Analytics Data API' in the API library and click 'Enable.' This must be done before any API calls will succeed. Step 2 — Create a service account: In the Google Cloud Console, go to IAM & Admin → Service Accounts. Click 'Create Service Account.' Give it a name (e.g., 'bolt-analytics-reader'), click 'Create and Continue,' and skip the optional role grant at the Google Cloud level (you will grant GA access separately). Click 'Done.' Step 3 — Download the key: Click on your new service account in the list. Go to the 'Keys' tab. Click 'Add Key' → 'Create new key' → 'JSON.' This downloads a JSON file containing the service account credentials. This file contains a private key — keep it secure and never commit it to version control. Step 4 — Grant GA4 access: Go to analytics.google.com. Open Admin → Property → Property Access Management. Click the '+' button to add a user. Enter the service account email (it ends in @your-project.iam.gserviceaccount.com). Select 'Viewer' role. Click 'Add.' The service account can now read your GA4 data. Step 5 — Add to .env: The service account key JSON is a multi-line file. Convert it to a single-line string by stringifying it: `JSON.stringify(keyFileContent)`. Store the entire stringified JSON as `GOOGLE_SERVICE_ACCOUNT_KEY` in your .env file. Your API route parses it back to JSON at runtime.

Bolt.new Prompt

Add GOOGLE_SERVICE_ACCOUNT_KEY and GA4_PROPERTY_ID to .env. Create a lib/ga4.ts file that exports a getGA4Client() function. It should parse GOOGLE_SERVICE_ACCOUNT_KEY from process.env as JSON, create a JWT-signed bearer token using the service account's private key and client_email for the scope https://www.googleapis.com/auth/analytics.readonly, and return a fetch helper that includes the bearer token. Cache the JWT token for 55 minutes. Export a ga4Query function that accepts a GA4 runReport request body and returns the report response.

Paste this in Bolt.new chat

lib/ga4.ts
1// lib/ga4.ts
2import crypto from 'crypto';
3
4const GA4_API_BASE = 'https://analyticsdata.googleapis.com/v1beta';
5
6let tokenCache: { token: string; expiresAt: number } | null = null;
7
8async function getServiceAccountToken(): Promise<string> {
9 if (tokenCache && Date.now() < tokenCache.expiresAt - 60_000) {
10 return tokenCache.token;
11 }
12
13 const keyRaw = process.env.GOOGLE_SERVICE_ACCOUNT_KEY;
14 if (!keyRaw) throw new Error('GOOGLE_SERVICE_ACCOUNT_KEY is not configured');
15
16 const serviceAccount = JSON.parse(keyRaw) as {
17 client_email: string;
18 private_key: string;
19 token_uri: string;
20 };
21
22 const now = Math.floor(Date.now() / 1000);
23 const header = Buffer.from(JSON.stringify({ alg: 'RS256', typ: 'JWT' })).toString('base64url');
24 const payload = Buffer.from(JSON.stringify({
25 iss: serviceAccount.client_email,
26 scope: 'https://www.googleapis.com/auth/analytics.readonly',
27 aud: serviceAccount.token_uri || 'https://oauth2.googleapis.com/token',
28 exp: now + 3600,
29 iat: now,
30 })).toString('base64url');
31
32 const signingInput = `${header}.${payload}`;
33 const sign = crypto.createSign('RSA-SHA256');
34 sign.update(signingInput);
35 const signature = sign.sign(
36 serviceAccount.private_key.replace(/\\n/g, '\n'),
37 'base64url'
38 );
39 const jwt = `${signingInput}.${signature}`;
40
41 const tokenResponse = await fetch('https://oauth2.googleapis.com/token', {
42 method: 'POST',
43 headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
44 body: new URLSearchParams({
45 grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
46 assertion: jwt,
47 }),
48 });
49
50 const tokens = await tokenResponse.json() as { access_token?: string; error?: string };
51 if (!tokens.access_token) {
52 throw new Error(`GA4 auth failed: ${tokens.error || 'unknown error'}`);
53 }
54
55 tokenCache = { token: tokens.access_token, expiresAt: Date.now() + 3600 * 1000 };
56 return tokens.access_token;
57}
58
59export async function ga4Query<T = unknown>(body: Record<string, unknown>): Promise<T> {
60 const accessToken = await getServiceAccountToken();
61 const propertyId = process.env.GA4_PROPERTY_ID;
62 if (!propertyId) throw new Error('GA4_PROPERTY_ID is not configured');
63
64 const response = await fetch(
65 `${GA4_API_BASE}/properties/${propertyId}:runReport`,
66 {
67 method: 'POST',
68 headers: {
69 Authorization: `Bearer ${accessToken}`,
70 'Content-Type': 'application/json',
71 },
72 body: JSON.stringify(body),
73 }
74 );
75
76 if (!response.ok) {
77 const error = await response.json().catch(() => ({})) as { error?: { message: string } };
78 throw new Error(error.error?.message || `GA4 API error ${response.status}`);
79 }
80
81 return response.json() as Promise<T>;
82}

Pro tip: Store the service account JSON as a single-line string in .env by running: node -e "process.stdout.write(JSON.stringify(require('./service-account.json')))" and copying the output. The JSON.stringify removes all newlines that would break the .env file format. Never commit the original JSON key file.

Expected result: The GA4 authentication helper successfully obtains access tokens. A test call to ga4Query with a simple metrics request (activeUsers) returns data from your GA4 property without authentication errors.

3

Build the GA4 Analytics Dashboard

With authentication working, build the analytics API routes and dashboard components. The GA4 Data API uses a `runReport` request that specifies dimensions (what to group by), metrics (what to measure), and a date range. The response returns rows with dimension values and metric values matched by index to the header rows. Key GA4 metrics for a standard analytics dashboard: `sessions`, `screenPageViews`, `totalUsers`, `newUsers`, `bounceRate`, `averageSessionDuration`, `engagementRate`, `conversions`. Dimensions for breakdown: `date` (YYYY-MM-DD), `pagePath`, `deviceCategory`, `country`, `city`, `sessionSource`, `sessionMedium`. For a 30-day session trend, use dimension `date` and metric `sessions` with `dateRange: {startDate: '30daysAgo', endDate: 'today'}`. GA4 returns one row per day with the date string and session count. Sort by date ascending for a time-series chart. For top pages, use dimension `pagePath` and metric `screenPageViews` sorted by screenPageViews descending with limit 10. This returns the most visited pages in your property. GA4's response format is verbose — dimensions and metrics are returned as separate arrays, with rows referencing them by index. A helper function that transforms the GA4 response format into flat key-value objects significantly simplifies working with the data in React components. GA4 API calls are outbound HTTPS requests and work in Bolt's WebContainer during development. You can build the full analytics dashboard and see real data from your GA4 property without deploying.

Bolt.new Prompt

Create Next.js API routes for GA4 analytics. Build /api/analytics/overview that fetches: total sessions, pageviews, users, bounce rate for 'last30days'; daily session breakdown (date + sessions) for a line chart; and top 10 pages by pageviews. Use the ga4Query helper from lib/ga4.ts. Parse GA4's dimension/metric response format into flat objects. Build a React AnalyticsDashboard page with: 4 stat cards (sessions, pageviews, users, bounce rate), a Recharts LineChart of daily sessions, and a table of top pages with pageview count and percentage of total.

Paste this in Bolt.new chat

app/api/analytics/overview/route.ts
1// app/api/analytics/overview/route.ts
2import { NextResponse } from 'next/server';
3import { ga4Query } from '@/lib/ga4';
4
5interface GA4Row {
6 dimensionValues: Array<{ value: string }>;
7 metricValues: Array<{ value: string }>;
8}
9
10interface GA4ReportResponse {
11 rows?: GA4Row[];
12 rowCount?: number;
13 dimensionHeaders?: Array<{ name: string }>;
14 metricHeaders?: Array<{ name: string; type: string }>;
15}
16
17function parseGA4Rows(
18 report: GA4ReportResponse
19): Array<Record<string, string>> {
20 if (!report.rows) return [];
21 const dimHeaders = (report.dimensionHeaders || []).map((h) => h.name);
22 const metHeaders = (report.metricHeaders || []).map((h) => h.name);
23
24 return report.rows.map((row) => {
25 const obj: Record<string, string> = {};
26 row.dimensionValues.forEach((d, i) => { obj[dimHeaders[i]] = d.value; });
27 row.metricValues.forEach((m, i) => { obj[metHeaders[i]] = m.value; });
28 return obj;
29 });
30}
31
32export async function GET() {
33 try {
34 const [summaryReport, trendsReport, pagesReport] = await Promise.all([
35 // Summary metrics
36 ga4Query<GA4ReportResponse>({
37 dateRanges: [{ startDate: '30daysAgo', endDate: 'today' }],
38 metrics: [
39 { name: 'sessions' },
40 { name: 'screenPageViews' },
41 { name: 'totalUsers' },
42 { name: 'bounceRate' },
43 { name: 'averageSessionDuration' },
44 ],
45 }),
46 // Daily trend
47 ga4Query<GA4ReportResponse>({
48 dateRanges: [{ startDate: '30daysAgo', endDate: 'today' }],
49 dimensions: [{ name: 'date' }],
50 metrics: [{ name: 'sessions' }, { name: 'totalUsers' }],
51 orderBys: [{ dimension: { dimensionName: 'date' } }],
52 }),
53 // Top pages
54 ga4Query<GA4ReportResponse>({
55 dateRanges: [{ startDate: '30daysAgo', endDate: 'today' }],
56 dimensions: [{ name: 'pagePath' }],
57 metrics: [{ name: 'screenPageViews' }, { name: 'sessions' }],
58 orderBys: [{ metric: { metricName: 'screenPageViews' }, desc: true }],
59 limit: 10,
60 }),
61 ]);
62
63 const summaryRows = parseGA4Rows(summaryReport);
64 const summary = summaryRows[0] || {};
65
66 const trends = parseGA4Rows(trendsReport).map((row) => ({
67 date: row.date,
68 sessions: parseInt(row.sessions || '0', 10),
69 users: parseInt(row.totalUsers || '0', 10),
70 }));
71
72 const pages = parseGA4Rows(pagesReport).map((row) => ({
73 path: row.pagePath,
74 pageviews: parseInt(row.screenPageViews || '0', 10),
75 sessions: parseInt(row.sessions || '0', 10),
76 }));
77
78 return NextResponse.json({
79 summary: {
80 sessions: parseInt(summary.sessions || '0', 10),
81 pageviews: parseInt(summary.screenPageViews || '0', 10),
82 users: parseInt(summary.totalUsers || '0', 10),
83 bounceRate: parseFloat(summary.bounceRate || '0'),
84 avgDuration: parseFloat(summary.averageSessionDuration || '0'),
85 },
86 trends,
87 pages,
88 });
89 } catch (err) {
90 const message = err instanceof Error ? err.message : 'Analytics fetch failed';
91 return NextResponse.json({ error: message }, { status: 500 });
92 }
93}

Pro tip: GA4 data processing can take up to 24-48 hours for some metrics (especially 'yesterday' data). For real-time analytics, use the GA4 Realtime API (runRealtimeReport) which shows data from the last 30 minutes. The realtime API uses different metrics like activeUsers and screenPageViewsPerUser.

Expected result: The analytics overview route returns real GA4 data including session counts, pageviews, and top pages. The React dashboard displays a line chart of daily sessions and a table of top pages. All data is fetched from your real Google Analytics property.

Common use cases

Embedded Analytics Dashboard for Clients

Build a client portal where customers can view their analytics reports without accessing Google Data Studio directly. Embed the Looker Studio report in a React component with your brand's layout surrounding it. Use URL parameters to pre-filter the report by client-specific dimensions. This gives clients a polished, branded analytics experience while you maintain the reports in Looker Studio's familiar interface.

Bolt.new Prompt

Build an embedded analytics page for my Bolt app. Create a LookerStudioEmbed React component that accepts a reportUrl prop, an optional dateRange prop ('last7days', 'last30days', 'last90days'), and a height prop (default 600px). The component should append the dateRange as a URL parameter to the report URL and render it in a responsive iframe with loading state. Add a toolbar above the iframe with the date range selector buttons. Handle the iframe load event to hide the loading spinner. Show the iframe in a card container with a 'Powered by Looker Studio' footer.

Copy this prompt to try it in Bolt.new

Custom Google Analytics Dashboard with GA4 API

Build a branded analytics dashboard that fetches sessions, pageviews, users, bounce rate, and top pages directly from the GA4 API. Render the data with your own Recharts charts instead of Looker Studio's default styling. Combine GA data with your own app metrics for a unified reporting view that Looker Studio's connectors cannot provide.

Bolt.new Prompt

Build a custom Google Analytics dashboard using GA4 Data API. Create a Next.js API route at /api/analytics/overview that authenticates with a Google service account (GOOGLE_SERVICE_ACCOUNT_KEY from process.env) and calls the GA4 Data API to fetch: sessions, pageviews, users, bounce rate, and top 10 pages for a given date range. Return formatted metrics and a pages array sorted by pageviews. Build a React dashboard with a metrics summary row (4 stat cards), a line chart of daily sessions over the date range using Recharts, and a sortable table of top pages.

Copy this prompt to try it in Bolt.new

GA4 Event Tracking Dashboard

Pull custom event data from GA4 to build a conversion funnel visualization or event frequency dashboard. Track specific user actions (button clicks, form submissions, video plays) and display them as a funnel chart or time-series graph. Useful for product teams monitoring feature adoption and conversion rates.

Bolt.new Prompt

Create a GA4 event tracking dashboard. Build /api/analytics/events that fetches event data from GA4 Data API for a specific event name (passed as query param). Use GOOGLE_SERVICE_ACCOUNT_KEY for auth. Return event count by day, total count, unique users who triggered it, and top associated pages. Build a React EventDashboard component that shows total event count, a line chart of daily events over the last 30 days, a breakdown by device category (mobile/desktop/tablet), and a table of top pages where the event occurred.

Copy this prompt to try it in Bolt.new

Troubleshooting

Looker Studio report shows 'You need access' inside the iframe

Cause: The report is not shared with public access. When an unauthenticated user views the embed, Looker Studio checks sharing permissions and shows an access error if they are not authorized.

Solution: Open the Looker Studio report, click Share → Change to 'Anyone with the link' → Viewer. Save the sharing settings. The embed will now load for anyone who has the URL, without requiring Google account sign-in.

GA4 API returns 403 Forbidden — The caller does not have permission

Cause: The service account has not been granted viewer access to the GA4 property. Creating the service account in Google Cloud does not automatically grant it access to Google Analytics.

Solution: Go to analytics.google.com → Admin → Property → Property Access Management. Click '+' to add a user. Enter the service account email (format: name@project.iam.gserviceaccount.com). Select 'Viewer' role. Click 'Add.' Wait 2-3 minutes for permissions to propagate.

GA4 API JWT authentication fails with 'invalid_grant' or 'invalid JWT'

Cause: The service account key JSON was not properly formatted when stored in the .env file. Multi-line private keys in the JSON get corrupted when stored as environment variables without proper escaping.

Solution: Convert the service account JSON to a single-line string: node -e "process.stdout.write(JSON.stringify(require('./key.json')))" and use the output as GOOGLE_SERVICE_ACCOUNT_KEY. The JSON.stringify escapes all newlines in the private key to \n literal characters.

typescript
1// In your API route, restore the newlines after parsing:
2const serviceAccount = JSON.parse(process.env.GOOGLE_SERVICE_ACCOUNT_KEY || '{}');
3// The private_key field will have \n literals from JSON.stringify
4// Restore them for the crypto module:
5const privateKey = serviceAccount.private_key.replace(/\\n/g, '\n');

GA4 runReport returns empty rows array even though the property has data

Cause: The GA4_PROPERTY_ID is incorrect, using the old Universal Analytics property ID instead of the GA4 property ID, or the date range has no data.

Solution: Verify the GA4 property ID in Google Analytics Admin → Property → Property Details. The GA4 property ID is a numeric string (not the tracking ID starting with UA-). Also check the date range — data from today may not be fully processed yet; use 'yesterday' as the end date for reliable data.

typescript
1// GA4 property IDs are numeric: '123456789'
2// NOT the old UA-XXXXXXXXX format
3// Find it in GA Admin → Property Settings → Property ID
4GA4_PROPERTY_ID=123456789

Best practices

  • For simple reporting use cases, Looker Studio iframe embedding is significantly faster to implement than building a custom GA4 API dashboard — use the embed approach unless you specifically need custom styling or data combination.
  • Store GOOGLE_SERVICE_ACCOUNT_KEY as a server-side environment variable without NEXT_PUBLIC_ prefix — the service account key is a private key that grants access to your analytics data.
  • Cache GA4 API responses for 5-15 minutes in your API routes — analytics data does not update in real time and repeated calls waste API quota while adding latency.
  • Always convert the service account JSON to a single-line string with JSON.stringify before storing in .env — multi-line private keys get corrupted in standard .env file format.
  • Grant service accounts only 'Viewer' access to GA4 properties — they do not need to create, edit, or delete data, and minimum-privilege access reduces the impact of credential exposure.
  • Use GA4's date range shortcuts (today, yesterday, 7daysAgo, 30daysAgo) rather than hardcoded dates in your API routes so reports always show fresh data relative to the current date.
  • When the Looker Studio embed shows an access error, check both the report's sharing settings and whether it is embedded from the correct /embed/ URL path rather than the /reporting/ browser URL.
  • Note the naming difference for documentation and user communication: the product is 'Looker Studio' (since October 2022) even though many users still call it 'Google Data Studio.' Both names refer to the same product.

Alternatives

Frequently asked questions

Is Google Data Studio the same as Looker Studio?

Yes — Google renamed Google Data Studio to Looker Studio in October 2022. The product, its features, and its API are identical. The URL changed from datastudio.google.com to lookerstudio.google.com. Existing reports continued to work without any changes required. Some documentation and forum posts still use the old name.

Do I need an API key to embed a Looker Studio report?

No — Looker Studio iframe embedding requires only a sharing setting change, not an API key. Set the report to 'Anyone with the link can view,' get the embed URL from the report's embed dialog, and use it in an iframe. No credentials, no backend code, and no rate limits for basic embed use.

Can I embed a Looker Studio report in Bolt's WebContainer preview?

Yes — iframe embedding works in Bolt's WebContainer preview since it is a standard HTML feature supported by all browsers. The GA4 Data API calls also work in the preview since they are outbound HTTPS requests. The only limitation is if Looker Studio's CSP (Content Security Policy) headers prevent iframe embedding on certain origins — most reports embed fine.

What is the difference between using Looker Studio embed and the GA4 Data API?

Looker Studio embed shows an existing pre-built report inside an iframe — the report is managed in Looker Studio's interface, non-technical users can build and maintain it, and you have no control over its visual design. GA4 Data API fetches raw analytics data that you render with your own charts — full visual control, ability to combine with other data, but requires development work and authentication setup.

How do I find my GA4 property ID?

In Google Analytics, click Admin (the gear icon) in the bottom left. In the Property column, click 'Property Settings.' The Property ID is shown at the top of the settings page as a numeric string like 123456789 — this is different from the old UA-XXXXXXXXX tracking ID format. Copy the numeric property ID into your GA4_PROPERTY_ID environment variable.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.