# How to Configure OpenSEO to Connect with Google Analytics 4: Hosted and Self-Hosted Setup

> Learn to connect OpenSEO with Google Analytics 4. This guide covers hosted and self-hosted setups, including API enablement and authentication via project settings.

- Repository: [Every App/open-seo](https://github.com/every-app/open-seo)
- Tags: how-to-guide
- Published: 2026-08-15

---

**To configure OpenSEO for Google Analytics 4, enable the Google Analytics Admin and Data APIs, set your `GOOGLE_CLIENT_ID` and `GOOGLE_CLIENT_SECRET` environment variables for self-hosted deployments, then use the project settings UI to authenticate and select your GA4 property.**

OpenSEO supports both hosted (managed) and self-hosted authentication modes for Google Analytics 4 integration. The connection uses OAuth 2.0 with read-only scopes to pull analytics data into your SEO dashboards without requiring write permissions.

## Configuration Architecture

OpenSEO implements GA4 connectivity through a provider-based OAuth system defined in [`src/shared/ga4.ts`](https://github.com/every-app/open-seo/blob/main/src/shared/ga4.ts). The integration supports two distinct operational modes:

**Hosted Mode (Default):** Uses OpenSEO's pre-configured Better Auth OAuth application. When users click **Connect with Google**, the system redirects through the managed authentication flow via `authClient.oauth2.link`.

**Self-Hosted Mode:** Requires you to provide your own Google Cloud OAuth 2.0 credentials via environment variables. This mode is mandatory for private infrastructure deployments and is handled by `startSelfHostedGa4Link` in [`src/serverFunctions/ga4.ts`](https://github.com/every-app/open-seo/blob/main/src/serverFunctions/ga4.ts).

The architecture separates concerns between shared constants ([`src/shared/ga4.ts`](https://github.com/every-app/open-seo/blob/main/src/shared/ga4.ts)), client-side initiation ([`src/client/features/integrations/startGoogleLink.ts`](https://github.com/every-app/open-seo/blob/main/src/client/features/integrations/startGoogleLink.ts)), and server-side API operations ([`src/serverFunctions/ga4.ts`](https://github.com/every-app/open-seo/blob/main/src/serverFunctions/ga4.ts)).

## Prerequisites and Environment Setup

Before connecting GA4, you must enable the required APIs and configure authentication credentials.

### Enable Google Analytics APIs

1. Visit the Google Cloud Console for your project.
2. Enable the **Google Analytics Admin API** and **Google Analytics Data API**.
3. Ensure your Google account has **Viewer** or higher permissions on the target GA4 properties.

### Configure OAuth Credentials (Self-Hosted Only)

If running OpenSEO on your own infrastructure:

1. Create a **Web application** OAuth 2.0 client ID in Google Cloud Console.
2. Add the authorized redirect URI: `https://your-domain.com/api/ga4/oauth/callback`.
3. Copy the Client ID and Client Secret for the next step.

### Set Environment Variables

Add the following to your deployment environment (e.g., `.env` file for Docker or Cloudflare Workers):

```bash
GOOGLE_CLIENT_ID=your-web-app-client-id
GOOGLE_CLIENT_SECRET=your-web-app-client-secret
BETTER_AUTH_SECRET=your-random-32-character-secret

```

Generate the `BETTER_AUTH_SECRET` using:

```bash
openssl rand -base64 32

```

For hosted deployments, omit the Google credential variables; the platform uses its managed OAuth application automatically.

## Connecting GA4 to Your Project

Once environment variables are configured, connect your property through the OpenSEO interface:

1. Navigate to **Project Settings → Analytics**.
2. Click **Connect with Google**.
3. Approve the read-only OAuth scopes (`analytics.readonly`) on Google's consent screen.
4. Select your GA4 property from the list populated by `listGa4Properties`.

This UI workflow calls `startGoogleLink("ga4", callbackURL)` from [`src/client/features/integrations/startGoogleLink.ts`](https://github.com/every-app/open-seo/blob/main/src/client/features/integrations/startGoogleLink.ts), which detects your authentication mode via `isHostedClientAuthMode()` and routes to either the hosted OAuth handler or the self-hosted flow. After selection, the system invokes `setGa4Property` to persist the property binding, storing the encrypted OAuth tokens via Better Auth.

## Key Implementation Files

- **[`src/shared/ga4.ts`](https://github.com/every-app/open-seo/blob/main/src/shared/ga4.ts)** — Defines GA4 integration constants including the provider ID `ga4`, required scopes array, and `GA4_INTEGRATION` configuration flags.
- **[`src/client/features/integrations/startGoogleLink.ts`](https://github.com/every-app/open-seo/blob/main/src/client/features/integrations/startGoogleLink.ts)** — Exports the `startGoogleLink` function that initiates the OAuth flow and handles the redirect logic for both hosted and self-hosted modes.
- **[`src/serverFunctions/ga4.ts`](https://github.com/every-app/open-seo/blob/main/src/serverFunctions/ga4.ts)** — Contains server functions including `getGa4Connection`, `listGa4Properties`, `setGa4Property`, `disconnectGa4`, and `startSelfHostedGa4Link`.
- **[`docs/SELF_HOSTING_GOOGLE_ANALYTICS.md`](https://github.com/every-app/open-seo/blob/main/docs/SELF_HOSTING_GOOGLE_ANALYTICS.md)** — Official documentation for self-hosted deployments covering environment variable requirements and Google Cloud Console configuration.

## Code Examples

### Initiating Self-Hosted GA4 Authentication

When running in self-hosted mode, the server generates the authorization URL:

```typescript
// src/serverFunctions/ga4.ts
export const startSelfHostedGa4Link = createServerFn({ method: "POST" })
  .middleware(requireAuthenticatedContext)
  .validator(startSelfHostedLinkSchema)
  .handler(async ({ data, context }) => ({
    url: await createSelfHostedGoogleAuthorizationUrl({
      integration: GA4_INTEGRATION,
      user: { userId: context.userId, userEmail: context.userEmail },
      callbackURL: data.callbackURL,
      publicOrigin: getPublicOrigin(getRequest()),
    }),
  }));

```

### Client-Side OAuth Entry Point

The UI uses this unified function regardless of deployment mode:

```typescript
// src/client/features/integrations/startGoogleLink.ts
export async function startGoogleLink(
  provider: "gsc" | "ga4",
  callbackURL: string,
) {
  const config = googleProviders[provider];
  
  if (!isHostedClientAuthMode()) {
    const res = await config.startSelfHosted({ data: { callbackURL } });
    window.location.href = res.url;
    return;
  }
  
  const res = await authClient.oauth2.link({
    providerId: config.providerId,
    callbackURL,
  });
  
  if (res.data?.url) window.location.href = res.url;
}

```

### Setting the GA4 Property

After OAuth completion, the selected property is stored:

```typescript
// src/serverFunctions/ga4.ts
export const setGa4Property = createServerFn({ method: "POST" })
  .middleware(requireProjectContext)
  .validator(setPropertySchema)
  .handler(async ({ data, context }) => {
    const connection = await Ga4Service.setProperty({
      projectId: context.projectId,
      organizationId: context.organizationId,
      accountId: data.accountId,
      propertyId: data.propertyId,
      userId: context.userId,
    });
    
    return {
      connected: true as const,
      propertyId: connection.propertyId,
      propertyDisplayName: connection.propertyDisplayName,
    };
  });

```

## Troubleshooting Common Configuration Errors

- **`redirect_uri_mismatch`**: The callback URL in Google Cloud Console does not match your deployment origin exactly. **Fix:** Verify the scheme, domain, and path (`/api/ga4/oauth/callback`) match your `publicOrigin` setting.
- **No properties listed**: The Google Analytics Admin API is disabled or your account lacks permissions. **Fix:** Enable the Admin API and ensure your Google account has **Viewer** access on the target properties.
- **Connection expires frequently**: The OAuth client is in "Testing" status, issuing short-lived refresh tokens. **Fix:** Promote the OAuth app to "Production" status in Google Cloud Console.
- **Hosted mode unavailable**: The `GA4_INTEGRATION.pending` flag is set to `true` in [`src/shared/ga4.ts`](https://github.com/every-app/open-seo/blob/main/src/shared/ga4.ts). **Fix:** Wait for the hosted OAuth app approval or switch to self-hosted mode by configuring `GOOGLE_CLIENT_ID` and `GOOGLE_CLIENT_SECRET`.

## Summary

- OpenSEO offers **hosted** and **self-hosted** GA4 integration modes, configurable via environment variables.
- **Self-hosted** deployments require `GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET`, and `BETTER_AUTH_SECRET` for secure token encryption.
- The integration uses read-only OAuth scopes defined in [`src/shared/ga4.ts`](https://github.com/every-app/open-seo/blob/main/src/shared/ga4.ts) to securely access analytics data without write permissions.
- Core functions `startGoogleLink`, `startSelfHostedGa4Link`, and `setGa4Property` handle the authentication and property binding workflow.
- Refer to [`docs/SELF_HOSTING_GOOGLE_ANALYTICS.md`](https://github.com/every-app/open-seo/blob/main/docs/SELF_HOSTING_GOOGLE_ANALYTICS.md) for detailed Google Cloud Console setup instructions.

## Frequently Asked Questions

### What environment variables are required for self-hosted GA4 integration?

You must set `GOOGLE_CLIENT_ID` and `GOOGLE_CLIENT_SECRET` from your Google Cloud OAuth web application credentials, plus `BETTER_AUTH_SECRET` for token encryption. Generate the auth secret with `openssl rand -base64 32`. Hosted deployments do not require the Google credentials as they use the managed Better Auth application.

### Why does my GA4 connection show no available properties?

This occurs when the Google Analytics Admin API is disabled in Google Cloud Console, or your Google account lacks permissions on any GA4 properties. Enable the API and verify your account has at least **Viewer** access on the target properties. The `listGa4Properties` function filters results based on your authenticated Google identity.

### How do I switch from hosted to self-hosted GA4 authentication?

Set the `GOOGLE_CLIENT_ID` and `GOOGLE_CLIENT_SECRET` environment variables in your deployment. The `startGoogleLink` function in [`src/client/features/integrations/startGoogleLink.ts`](https://github.com/every-app/open-seo/blob/main/src/client/features/integrations/startGoogleLink.ts) automatically detects these variables via `isHostedClientAuthMode()` and routes to `startSelfHostedGa4Link` instead of the hosted OAuth flow. Restart your application after setting the variables.

### Is the GA4 integration read-only?

Yes. The integration requests only the `analytics.readonly` scope as defined in the `scopes` array within [`src/shared/ga4.ts`](https://github.com/every-app/open-seo/blob/main/src/shared/ga4.ts). OpenSEO cannot modify your GA4 configuration or data; it only pulls metrics for reporting purposes, storing encrypted tokens via Better Auth's account table.