# How to Integrate Google Search Console (GSC) with OpenSEO: Complete Setup Guide

> Integrate Google Search Console with OpenSEO seamlessly. Pull click, impression, and position data directly via OAuth 2.0. Get the complete setup guide for hosted and self-hosted deployments.

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

---

**OpenSEO supports a first-party Google Search Console integration that pulls click, impression, and position data directly from your Google account using OAuth 2.0 and the `webmasters.readonly` scope, working in both hosted and self-hosted deployments.**

OpenSEO is an open-source SEO platform that includes native support for Google Search Console. The integration allows you to analyze search performance without manually exporting CSV files, using a secure OAuth flow that encrypts tokens at rest. Whether you run the every-app/open-seo repository in the cloud or on your own infrastructure, the architecture supports seamless GSC data retrieval through a type-safe API layer.

## Prerequisites and Environment Configuration

Before initiating the OAuth flow, self-hosted instances must configure three critical environment variables to authenticate with Google’s API.

### Required Environment Variables

The integration requires the following variables in your deployment environment:

- **`GOOGLE_CLIENT_ID`** – Your OAuth 2.0 Client ID from Google Cloud Console
- **`GOOGLE_CLIENT_SECRET`** – The corresponding Client Secret
- **`BETTER_AUTH_SECRET`** – A sufficiently long encryption key used by Better-Auth to encrypt OAuth tokens at rest

In [`src/server/features/gsc/oauth-config.ts`](https://github.com/every-app/open-seo/blob/main/src/server/features/gsc/oauth-config.ts), the helper function `hasSelfHostedGscConfig` validates these variables before surfacing the "Connect with Google" UI. If any variable is missing, the integration remains disabled to prevent runtime errors.

### Google Cloud Project Setup

You must create a Google Cloud project and enable the **Google Search Console API**. Configure the OAuth consent screen as an *External* app and add authorized test users if your app remains in testing mode. Create OAuth 2.0 credentials (Web application type) and set the authorized redirect URI to `https://<your-domain>/api/gsc/oauth/callback`.

## OAuth Provider Architecture

OpenSEO uses Better-Auth to manage the OAuth handshake, with a dedicated provider configuration for Google Search Console.

### Better-Auth Integration

The OAuth provider is defined in [`src/shared/gsc.ts`](https://github.com/every-app/open-seo/blob/main/src/shared/gsc.ts) and consumed by the generic auth plugin in [`src/lib/auth-config.ts`](https://github.com/every-app/open-seo/blob/main/src/lib/auth-config.ts). The provider requests the `webmasters.readonly` scope, which grants read-only access to GSC data without permission to modify site settings.

```typescript
// Conceptual flow from src/shared/gsc.ts and src/lib/auth-config.ts
const gscProvider = {
  id: "google-search-console",
  scopes: ["webmasters.readonly"],
  // ... additional OAuth configuration
};

```

### Permission Scopes and Security

The **webmasters.readonly** scope limits OpenSEO to viewing search analytics, site listings, and URL inspection data. It cannot submit URL removals or change site configurations. All OAuth tokens are encrypted using `BETTER_AUTH_SECRET` before storage in the database, ensuring credentials remain secure even if database backups are compromised.

## Connecting Your GSC Account

The connection process involves two distinct phases: initial authorization and property selection.

### The Connection Flow

When users click **Integrations → GSC Insights → Connect with Google**, the client invokes `startSelfHostedGscLink` (exposed in [`src/serverFunctions/gsc.ts`](https://github.com/every-app/open-seo/blob/main/src/serverFunctions/gsc.ts)). This function:

1. Validates the self-hosted configuration via `hasSelfHostedGscConfig`
2. Constructs the authorization URL with the correct `state` parameter
3. Redirects the user to Google’s OAuth consent screen

After authorization, Google redirects to `/api/gsc/oauth/callback`, where the system exchanges the code for tokens and stores them via Better-Auth’s encrypted credential store.

### Selecting a Property

Once authenticated, the UI calls `listGscSites` (from [`src/serverFunctions/gsc.ts`](https://github.com/every-app/open-seo/blob/main/src/serverFunctions/gsc.ts)) to retrieve all verified properties. The endpoint returns an array of accounts with their associated sites, marking properties that require reconnection due to expired grants.

```typescript
// Example response from listGscSites
{
  accounts: [{
    accountId: "acct_456",
    email: "admin@example.com",
    requiresReconnect: false,
    sites: [{
      siteUrl: "https://example.com",
      isSelected: false,
      selectable: true
    }]
  }]
}

```

When a user selects a site, the client sends a POST request to `setGscSite`, which invokes `GscService.setSite` in [`src/server/features/gsc/services/GscService.ts`](https://github.com/every-app/open-seo/blob/main/src/server/features/gsc/services/GscService.ts). This persists the mapping in the `gsc_connection` table and associates the OAuth account with the specific OpenSEO project.

## Retrieving Search Console Data

After connection, OpenSEO provides several server functions to query GSC data programmatically.

### Service Layer Architecture

`GscService` ([`src/server/features/gsc/services/GscService.ts`](https://github.com/every-app/open-seo/blob/main/src/server/features/gsc/services/GscService.ts)) orchestrates all GSC operations. It wraps the low-level HTTP client defined in [`src/server/lib/gscClient.ts`](https://github.com/every-app/open-seo/blob/main/src/server/lib/gscClient.ts), which handles requests to `/sites` and `/searchAnalytics/query` endpoints. The service normalizes API errors into `GscApiError` and `GscTokenError` classes, allowing the UI to distinguish between permission issues and transient API failures.

### Available API Endpoints

The public API exposed through [`src/serverFunctions/gsc.ts`](https://github.com/every-app/open-seo/blob/main/src/serverFunctions/gsc.ts) includes:

- **`getGscGrantStatus`** – Checks if the current user has an active OAuth grant
- **`getGscConnection`** – Returns the selected property URL and connection state for a project
- **`listGscSites`** – Lists all accessible GSC properties across all connected Google accounts
- **`setGscSite`** – Binds a chosen property to a specific project
- **`disconnectGsc`** – Removes the project binding and revokes the OAuth account if unused by other projects

## Database Schema and Security

OpenSEO maintains a dedicated schema for GSC connections with encrypted credential storage.

### The gsc_connection Table

Defined in [`src/db/gsc.schema.ts`](https://github.com/every-app/open-seo/blob/main/src/db/gsc.schema.ts), the `gsc_connection` table stores:

- **project_id** – Foreign key to the OpenSEO project
- **site_url** – The selected GSC property URL
- **account_id** – Reference to the encrypted OAuth credential stored by Better-Auth
- **created_at/updated_at** – Timestamps for audit trails

### Token Encryption

Better-Auth encrypts OAuth access tokens and refresh tokens using the `BETTER_AUTH_SECRET` environment variable before writing them to the database. This means raw Google credentials never exist in plain text in the database or application logs.

## Step-by-Step Self-Hosted Setup

Follow these steps to integrate Google Search Console in a self-hosted OpenSEO instance:

1. **Create a Google Cloud project** and enable the Google Search Console API
2. **Configure OAuth consent** as an External app and add test users
3. **Create an OAuth client ID** (Web application type) and set the redirect URI to `https://<your-domain>/api/gsc/oauth/callback`
4. **Add environment variables** (`GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET`, `BETTER_AUTH_SECRET`) to your `.env` file or deployment dashboard
5. **Restart OpenSEO** to load the new configuration
6. **Navigate to Integrations → GSC Insights** and click "Connect with Google"
7. **Select a verified property** from the site picker populated by `listGscSites`
8. **Verify connection** by checking that the GSC Insights dashboard populates with click and impression data

## Programmatic API Usage

You can interact with the GSC integration programmatically using the server functions. These examples assume an authenticated session with appropriate cookies.

### Check Grant Status

```typescript
const status = await fetch('/api/gsc/grant-status', { method: 'GET' })
  .then(r => r.json());
// Returns: { connected: true } or { connected: false }

```

### Retrieve Connection Details

```typescript
const connection = await fetch('/api/gsc/connection', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ projectId: 'proj_123' })
}).then(r => r.json());
// Returns: { connected: true, siteUrl: 'https://example.com', ... }

```

### List Available Sites

```typescript
const sites = await fetch('/api/gsc/sites', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ projectId: 'proj_123' })
}).then(r => r.json());
// Returns: { accounts: [{ accountId, email, requiresReconnect, sites: [...] }] }

```

### Bind a Site to Project

```typescript
const result = await fetch('/api/gsc/set-site', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    projectId: 'proj_123',
    accountId: 'acct_456',
    siteUrl: 'https://example.com'
  })
}).then(r => r.json());
// Returns: { connected: true, siteUrl: 'https://example.com' }

```

### Disconnect GSC

```typescript
const result = await fetch('/api/gsc/disconnect', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ projectId: 'proj_123' })
}).then(r => r.json());
// Returns: { connected: false }

```

## Summary

- **OpenSEO** integrates Google Search Console through a first-party OAuth 2.0 flow using the `webmasters.readonly` scope
- **Configuration** requires three environment variables: `GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET`, and `BETTER_AUTH_SECRET`
- **Architecture** includes a Better-Auth provider in [`src/shared/gsc.ts`](https://github.com/every-app/open-seo/blob/main/src/shared/gsc.ts), a service layer in [`src/server/features/gsc/services/GscService.ts`](https://github.com/every-app/open-seo/blob/main/src/server/features/gsc/services/GscService.ts), and API endpoints in [`src/serverFunctions/gsc.ts`](https://github.com/every-app/open-seo/blob/main/src/serverFunctions/gsc.ts)
- **Security** relies on Better-Auth’s encryption for OAuth tokens and the `gsc_connection` table in [`src/db/gsc.schema.ts`](https://github.com/every-app/open-seo/blob/main/src/db/gsc.schema.ts) for project mappings
- **Self-hosted** deployments must configure Google Cloud OAuth credentials with the redirect URI pointing to `/api/gsc/oauth/callback`

## Frequently Asked Questions

### Do I need to self-host OpenSEO to use Google Search Console integration?

No. OpenSEO supports GSC integration in both hosted and self-hosted deployments. However, self-hosted instances require additional configuration of Google Cloud OAuth credentials and environment variables, while the hosted version handles OAuth provider setup automatically.

### What permissions does OpenSEO request from my Google account?

OpenSEO requests the `webmasters.readonly` OAuth scope, which grants read-only access to your Search Console data. This permission allows OpenSEO to retrieve click counts, impressions, and position data, but cannot modify site settings, remove URLs, or submit sitemaps.

### Where are my Google OAuth tokens stored?

OAuth tokens are encrypted at rest using your `BETTER_AUTH_SECRET` environment variable and stored in the database managed by Better-Auth. The `gsc_connection` table in [`src/db/gsc.schema.ts`](https://github.com/every-app/open-seo/blob/main/src/db/gsc.schema.ts) only stores references to these encrypted credentials, not the tokens themselves.

### Can I connect multiple Google Search Console properties to one OpenSEO project?

No, each OpenSEO project binds to a single GSC property URL at a time. However, you can disconnect and reconnect to different properties using the `setGscSite` endpoint, or create separate OpenSEO projects for different GSC properties. The `listGscSites` endpoint shows all properties available across all Google accounts connected to your user.