# How OpenSEO Integrates with GA4 for SEO Visibility: Technical Architecture Guide

> Learn how OpenSEO integrates with GA4 to enhance SEO visibility. Discover the technical architecture for correlating GA4 engagement with Search Console data.

- Repository: [Every App/open-seo](https://github.com/every-app/open-seo)
- Tags: architecture
- Published: 2026-09-01

---

**OpenSEO enriches SEO reports with first-party analytics by connecting to Google Analytics 4 through Better Auth OAuth, then correlating GA4 engagement metrics with Google Search Console data on a normalized host-plus-path key.**

OpenSEO bridges search performance and user engagement by integrating Google Analytics 4 (GA4) directly into its reporting pipeline. This integration allows digital marketers to move beyond vanity metrics, combining GSC impression data with GA4 revenue, conversion events, and engagement rates. By analyzing these datasets through the `get_search_opportunities` tool, teams can identify high-traffic pages that underperform in user retention or monetization.

## High-Level Architecture

The integration follows a layered architecture that separates data persistence from business logic and transport adapters.

**Ga4ConnectionRepository** persists the project-to-property mapping in SQLite or Postgres, storing the canonical resource name (`properties/{id}`), display name, time-zone, and currency according to the schema defined in [`specs/0007-google-analytics-mcp-integration.md`](https://github.com/every-app/open-seo/blob/main/specs/0007-google-analytics-mcp-integration.md) (lines 95-103).

**Ga4Service** handles all Google API interactions. It retrieves the Better Auth grant from the `genericOAuth` provider (`google-analytics`), verifies the selected property via the Google Analytics **Admin API** (`accountSummaries.list` → `properties.get`), and constructs fixed Data API `runReport` requests. The service normalizes date ranges, enforces privacy limits, and joins GA4 rows to GSC rows using a host-plus-path key.

**Server-function and MCP adapters** expose the tools as TanStack server functions and Model Context Protocol (MCP) endpoints. These adapters handle input validation via Zod, project authorization, and error mapping according to the error contract defined at line 330 of the specification.

## GA4 Connection Lifecycle

Establishing a connection requires four distinct steps that secure user consent and validate property access.

### 1. OAuth Grant Creation

The UI triggers a Better Auth flow using the `google-analytics` provider. Required scopes include `openid`, `email`, `profile`, and `https://www.googleapis.com/auth/analytics.readonly` as specified in [`specs/0007-google-analytics-mcp-integration.md`](https://github.com/every-app/open-seo/blob/main/specs/0007-google-analytics-mcp-integration.md) (lines 60-66). The resulting grant is stored securely and reused for subsequent API calls.

### 2. Property Discovery

The service paginates through `accountSummaries.list` to retrieve available accounts, then calls `properties.get` to fetch the selected property's time-zone and currency. Only the UI can modify the selected property; MCP tools accept a `projectId` parameter and rely on the stored mapping (lines 84-89).

### 3. Mapping Persistence

A unique `ga4_connections` row per project stores the property metadata. This row links the OpenSEO project to the GA4 property resource name, enabling the system to route Data API requests to the correct analytics container (lines 95-103).

### 4. Disconnect and Cleanup

Deleting the mapping triggers a check for grant reusability. If no other project references the same Better Auth grant, the system removes the OAuth credentials entirely to maintain security hygiene (lines 112-115).

## Fixed-Report Data Tools

OpenSEO exposes four read-only MCP tools that query predefined GA4 reports. Each tool filters for the `sessionDefaultChannelGroup = Organic Search` dimension unless specified otherwise.

**`get_google_analytics_organic_landing_pages`** returns `hostName` and `landingPage` dimensions alongside session counts, engaged sessions, engagement rates, key events, and purchase revenue. This tool answers which landing pages drive the most valuable organic traffic.

**`get_google_analytics_page_performance`** queries `hostName` and `pagePath` to deliver screen page views, active users, user engagement duration, and key events. Use this to diagnose content stickiness for specific URL patterns.

**`get_google_analytics_key_events`** breaks down `eventName` metrics with optional `hostName` and `landingPage` dimensions. This reveals which conversion events occur most frequently on organic landing pages.

**`get_search_opportunities`** performs a left join between GSC impressions/position data and the GA4 organic landing-page report. The tool normalizes URLs by lower-casing hosts, stripping schemes, queries, and fragments, and trimming trailing slashes to ensure accurate matching (lines 70-77). Unmatched GSC rows retain `joinStatus: "gsc_only"` with null GA4 metrics, ensuring SEO opportunities surface even without corresponding analytics data (lines 84-87).

## Data Correlation and Opportunity Scoring

The `get_search_opportunities` tool calculates an opportunity score by combining demand (GSC impressions), business value (GA4 revenue and key events), and reachability (average position) according to the formula outlined in lines 96-99 of the specification.

Date range handling requires careful alignment. The service clamps GA4 queries to the last 28 complete days while respecting the property's time-zone for GSC data. If time-zones differ between data sources, the system issues a warning but proceeds with the analysis (lines 48-51). Response envelopes include quota transparency data—tokens per day/hour and concurrent request limits—allowing clients to throttle usage proactively.

## Error Handling and Privacy Protections

The integration maps Google API errors to stable domain error codes for consistent client-side handling.

- **`ga4_not_connected`**: Returned when no `ga4_connections` row exists for the requested project.
- **`ga4_reconnect_required`**: Indicates OAuth token minting failure, typically caused by an `invalid_grant` or expired refresh token.
- **`ga4_quota_exhausted`**: Signals Google API rate limiting (HTTP 429 or `RESOURCE_EXHAUSTED`).
- **`ga4_report_incompatible`**: Occurs when the fixed-report builder rejects a request due to incompatible dimensions or metrics.

Privacy safeguards ensure raw OAuth credentials, account IDs, and full API request bodies never reach the client. The service forwards only aggregated, allowed fields, with sensitive parameters filtered at the server-function layer (lines 44-48).

## Implementation Examples

Below are practical TypeScript examples demonstrating client interaction with the OpenSEO GA4 integration. These assume a configured TanStack server-function client and valid `projectId`.

```typescript
// Connect a GA4 property (one-time setup)
await fetch(`/api/ga4/oauth/callback`, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    projectId: "proj_123",
    propertyId: "properties/987654321",
  }),
});

```

```typescript
// Retrieve organic landing-page metrics
const organic = await fetch(
  `/api/mcp/get_google_analytics_organic_landing_pages`,
  {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ 
      projectId: "proj_123", 
      startDate: "2024-05-01", 
      endDate: "2024-05-28" 
    }),
  },
).then((r) => r.json());

```

```typescript
// Get combined SEO opportunities (GSC + GA4)
const opportunities = await fetch(
  `/api/mcp/get_search_opportunities`,
  {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      projectId: "proj_123",
      startDate: "2024-05-01",
      endDate: "2024-05-28",
      limit: 50,
    }),
  },
).then((r) => r.json());

console.log("Top SEO opportunities:", opportunities.rows);

```

## Key Implementation Files

Reference these source files when extending or debugging the GA4 integration:

- **[`src/shared/ga4.ts`](https://github.com/every-app/open-seo/blob/main/src/shared/ga4.ts)**: Core GA4 REST client, token handling, and fixed-report builders.
- **[`src/shared/error-codes.ts`](https://github.com/every-app/open-seo/blob/main/src/shared/error-codes.ts)**: Centralized error-code definitions and Google API error mappings.
- **[`src/db/schema.ts`](https://github.com/every-app/open-seo/blob/main/src/db/schema.ts)**: Database schema including the `ga4_connections` table structure.
- **[`src/serverFunctions/searchPerformance.ts`](https://github.com/every-app/open-seo/blob/main/src/serverFunctions/searchPerformance.ts)**: TanStack server-function wrappers exposing MCP tools.
- **[`specs/0007-google-analytics-mcp-integration.md`](https://github.com/every-app/open-seo/blob/main/specs/0007-google-analytics-mcp-integration.md)**: Complete design specification for the integration architecture.

## Summary

- OpenSEO integrates GA4 via Better Auth OAuth with scopes limited to read-only analytics access.
- The `Ga4Service` class manages property verification through the Google Analytics Admin API and constructs fixed Data API reports.
- Four specialized tools query organic landing pages, page performance, key events, and combined search opportunities.
- The `get_search_opportunities` tool joins GSC and GA4 data using normalized URL keys to surface high-demand, underperforming pages.
- Comprehensive error handling covers connection states, quota limits, and token expiration without exposing sensitive credentials.

## Frequently Asked Questions

### What OAuth scopes does OpenSEO require for GA4 integration?

OpenSEO requests `openid`, `email`, `profile`, and `https://www.googleapis.com/auth/analytics.readonly` during the Better Auth flow. These read-only scopes prevent write access to the GA4 property while allowing the service to fetch account summaries, property metadata, and report data.

### How does OpenSEO handle time-zone differences between GA4 and Google Search Console?

The system warns users when the GA4 property time-zone differs from the GSC property time-zone but proceeds with analysis using the respective time-zones for each data source. GA4 queries clamp to the last 28 complete days based on the property's time-zone, while GSC data respects its own configuration.

### What happens when GA4 API quota limits are reached?

When Google returns HTTP 429 or `RESOURCE_EXHAUSTED`, the service raises a `ga4_quota_exhausted` error with details about token limits per day/hour and concurrent request thresholds. Clients should implement exponential backoff and check the quota transparency data included in every response envelope.

### Can I analyze multiple GA4 properties within a single OpenSEO project?

No. The `ga4_connections` table enforces a one-to-one mapping between an OpenSEO project and a GA4 property. The unique constraint on `projectId` ensures data consistency and prevents authorization complexity. To analyze multiple properties, create separate OpenSEO projects for each GA4 container.