How OpenSEO Integrates with Google Search Console for Performance Data
OpenSEO integrates with Google Search Console through a layered architecture involving OAuth authentication via Better-Auth, a thin REST client wrapper around the Google Webmasters v3 API, and server-side service functions that expose performance metrics to the frontend.
OpenSEO provides first-party, free access to Google Search Console (GSC) performance data without requiring third-party API credits. This deep integration allows users to pull organic search metrics—clicks, impressions, CTR, and position—directly into their SEO dashboards. Below is a complete technical breakdown of how every-app/open-seo implements this flow, from initial OAuth grant to final data consumption.
OAuth Authentication and Account Linking
The integration begins when users grant OpenSEO read-only access to their Search Console properties. This flow uses Better-Auth for secure OAuth handling.
The provider constant lives in src/shared/gsc.ts, which defines:
export const GOOGLE_SEARCH_CONSOLE_PROVIDER_ID = "google-search-console";
When a user initiates connection, the server function startSelfHostedGscLink (in src/serverFunctions/gsc.ts) builds an authorization URL using the self-hosted flow. Upon successful authorization, Better-Auth stores the grant in the account table with the provider ID google-search-console.
Token Management and GSC REST Client
Every API call requires a fresh short-lived access token. The gscClient handles this automatically.
In src/server/lib/gscClient.ts, the client calls:
const token = await getAuth().api.getAccessToken({
accountId: account.id,
providerId: GOOGLE_SEARCH_CONSOLE_PROVIDER_ID,
});
Token failures—expired or revoked grants—are wrapped in GscTokenError. This allows upstream code to distinguish expected grant failures from unexpected errors.
The createGscClient factory returns a thin wrapper around https://www.googleapis.com/webmasters/v3 with three core methods:
listSites()– Enumerates all verified properties the grant can accessquerySearchAnalytics()– ExecutessearchAnalytics.queryfor performance datainspectUrl()– Calls the URL Inspection API
Service Layer: GscService
GscService (src/server/features/gsc/services/GscService.ts) consolidates all GSC business logic. It exposes five high-level operations:
| Method | Purpose |
|---|---|
userHasGrant() |
Checks if a valid OAuth grant exists for the user |
listSitesForUserWithGrantStatus() |
Lists sites with grant status for each |
setSite() |
Stores the selected property for a project |
getPerformance() |
Fetches search analytics data with filtering |
inspectUrls() |
Runs URL Inspection API calls |
The service also implements error normalization. When GscTokenError bubbles up, isExpectedGrantFailure() returns true, signaling the UI to prompt reconnection without surfacing raw stack traces.
Server Functions for Frontend Consumption
OpenSEO exposes GSC functionality through TanStack server functions that React components consume via TanStack Query.
Connection Management (src/serverFunctions/gsc.ts)
// List all accessible GSC accounts and sites
const { accounts } = await listGscSites({ projectId: "proj_123" });
// Bind a selected property to the project
await setGscSite({
projectId: "proj_123",
siteUrl: "https://example.com",
accountId: "acc_456"
});
Performance Reporting (src/serverFunctions/searchPerformance.ts)
The getSearchPerformanceReport function orchestrates multiple GSC queries to build a complete dashboard dataset:
import { getSearchPerformanceReport } from "@/serverFunctions/searchPerformance";
const report = await getSearchPerformanceReport({
projectId: "proj_123",
dateRange: { preset: "last_28_days" },
device: "mobile",
country: "USA",
});
if (report.connected) {
console.log(report.totals); // Aggregated clicks, impressions, CTR, position
console.log(report.strikingDistance); // Query/page rows near ranking thresholds
console.log(report.countries); // Country breakdown
}
Behind this single call, getSearchPerformanceReport invokes GscService.getPerformance three times:
- Daily totals for current and previous periods (trending)
- Query/page rows filtered for "striking-distance" analysis (positions 4-20 with high impressions)
- Country breakdown for geographic insights
Direct Service Integration (Advanced Use)
For custom logic or testing, import GscService directly:
import { GscService } from "@/server/features/gsc/services/GscService";
const performance = await GscService.getPerformance({
projectId: "proj_123",
startDate: "2024-01-01",
endDate: "2024-01-31",
dimensions: ["query", "page"],
filters: [
{ dimension: "device", operator: "equals", expression: "MOBILE" }
],
rowLimit: 500,
});
// Raw GSC API response shape
console.log(performance.rows); // Array of { keys: [...], clicks, impressions, ctr, position }
Error Handling and Reconnection Flow
The integration gracefully handles token lifecycle edge cases:
- Missing grant → UI shows "Connect Google Search Console" card
- Expired/revoked token →
GscTokenErrortriggersisExpectedGrantFailure(), prompting reconnection without error spam - API quota exceeded → Standard HTTP error propagation with retry guidance
This design ensures users understand when re-authorization is needed versus when transient errors occur.
Summary
- OAuth flow uses Better-Auth with provider ID defined in
src/shared/gsc.ts, storing grants in theaccounttable - Token minting happens automatically in
src/server/lib/gscClient.tsviagetAuth().api.getAccessToken - REST client wraps Google Webmasters v3 API with
listSites,querySearchAnalytics, andinspectUrlmethods - Service layer (
GscService) provides high-level operations: grant checks, site listing, property binding, and performance queries - Server functions in
src/serverFunctions/gsc.tsandsrc/serverFunctions/searchPerformance.tsexpose typed endpoints to the React frontend - Error handling distinguishes expected grant failures from unexpected errors, guiding users toward reconnection
Frequently Asked Questions
What OAuth scopes does OpenSEO request from Google Search Console?
OpenSEO requests read-only scopes for Search Console data. The exact scope configuration is defined alongside the provider ID in src/shared/gsc.ts. The integration never requests write access to properties or site settings.
Can OpenSEO access multiple GSC properties for one project?
Yes. A user can have multiple Google accounts connected via Better-Auth grants. The listGscSites server function returns all accessible properties across all linked accounts, and setGscSite binds one property per project. Switching properties requires calling setGscSite again with a different siteUrl.
How does OpenSEO handle GSC API rate limits?
The thin REST client in src/server/lib/gscClient.ts propagates standard HTTP errors from Google's API. The service layer does not implement custom retry logic—rate limit handling is currently delegated to calling code. For heavy usage, the rowLimit parameter in GscService.getPerformance allows pagination control to stay within quotas.
Is the raw GSC data cached or stored in OpenSEO's database?
Per the source analysis, performance data is not persisted in OpenSEO's database. Each dashboard view triggers fresh GSC API calls through GscService.getPerformance. Only the OAuth grant and selected property binding are stored (in account and GscConnectionRepository respectively). This design ensures data freshness while minimizing storage requirements.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →