Google Search Console - v1.0.0
Troubleshooting
Common errors and what to check first. If your problem isn't here, the FAQ covers the "why is this designed like that?" questions.
Error messages
The artisanpack-ui/google base package is required for Search Console reporting but is not installed or is not fully bootable.
Thrown from SearchAnalyticsClient::query() or the constructor's null $tokens. Means BaseInstalled::check() returned false.
Check:
- Is
artisanpack-ui/googleincomposer.json? Runcomposer show artisanpack-ui/google— expect the current version. - Did you run
composer install/composer dump-autoload? - In tinker:
class_exists( ArtisanPackUI\Google\Google::class )— should betrue. If false, autoloader isn't picking it up. - Long-running process (Octane, Horizon)?
BaseInstalled::reset()between requests clears the memoised result.
Google Search Console site URL is not configured. Set google-search-console.reporting.site_url or GSC_SITE_URL.
Thrown when neither the $siteUrl argument nor config supplied a value.
Check:
.env:GSC_SITE_URL="..."— set?- Run
php artisan config:clear— the config cache may be stale. - In tinker:
config('google-search-console.reporting.site_url')— should show the value.
Search Console authentication failed: ...
Thrown from ReportingException::authenticationFailed(). The base package's TokenManager threw a TokenRefreshException.
Check:
GoogleConnectionrow:status = 'connected'? If not, the user needs to reconnect.disconnect_reasoncolumn: if set, that's the base package's own explanation (typically "Refresh token revoked or expired.").- Did
APP_KEYrotate without re-encrypting the tokens? Every connection is dead if so — see the base package's FAQ.
Search Console API request failed to reach Google.
ReportingException::transportFailure(). Guzzle raised a ConnectionException.
Check:
- Is
googleapis.comreachable from your app server?curl -v https://searchconsole.googleapis.com/webmasters/v3/sites. - Firewall / proxy blocking outbound HTTPS?
google-search-console.reporting.timeouttoo low for slow networks? Bump it, or lower the range you're querying.
Search Console API returned an error (status 403).
ReportingException::apiError( 403, ... ). Google refused.
Check:
- The account has
siteOwnerorsiteFullUserpermission on the property.siteRestrictedUserisn't enough forsearchAnalytics.query. - The
webmasters.readonlyscope was granted. Users who connected before this package was installed need to reauthorize — the base package's connection UI surfaces this. - The property is verified (not just added-but-unverified).
Search Console API returned an error (status 404).
ReportingException::apiError( 404, ... ). The property URL doesn't match a verified property.
Check:
GSC_SITE_URLmatches one of thesiteUrlvalues fromsites.listverbatim. See Installation/Property Verification.- Trailing slashes,
httpvshttps,wwwprefix all matter.
Search Console API returned an error (status 429).
Rate limit hit. Google's Search Console API quota is per-account + per-property + per-day.
Check:
- Are multiple worker processes hitting the same property? Serialise via a queue.
- The
SearchAnalyticsClientcache should keep this rare — checkgoogle-search-console.reporting.cache_ttlis > 0. - If you must, throttle in your job scheduler (
RateLimitedmiddleware in Laravel queues).
Search Console API returned an error (status 500). (or 502/503)
Google-side. Retry with backoff — the client doesn't retry on its own.
Component symptoms
Livewire component shows "Install artisanpack-ui/google" persistently
$baseInstalled was set to false at mount. Base package isn't autoloadable. Same diagnosis as the BaseNotInstalledException message above.
Livewire component shows "Connect a Google account to view Search Console data."
GoogleConnectionResolver::forUser() returned null. The signed-in user has no google_connections row with status = connected.
Check:
- User hits
/google/auth/connect(base package route) → gets bounced back with a working row? - Row exists but
status = disconnected? Check thedisconnect_reasoncolumn. - Multi-tenant app? The connection is scoped by
user_id; if your tenant model doesn't reuse theUsermodel, overrideGoogleConnectionResolver— see API Reference/Support#GoogleConnectionResolver.
Component renders but every metric is 0
Data actually is zero, or Google's rate for the recent days isn't finalised yet.
Check:
hasDatapublic property (Livewire) /data.hasData(React/Vue) — false means Google returned no rows; the component should be rendering an empty state, not zeros. If you customized the view, make sure the empty branch renders.- Change the range to
90or180— new properties often show zeros for the trailing 2–3 days. - In tinker: run
PerformanceOverviewFetcher::fetch()directly and inspect the raw$data->totals.
React / Vue component always shows "Not authenticated"
Endpoint returned 401. The auth middleware failed.
Check:
- The fetch includes cookies:
credentials: 'same-origin'in your fetch options.fetchGscPerformance()sets this by default; if you callfetch()directly, add it. - Session cookie is valid — check DevTools → Application → Cookies.
CMS bridge widgets don't appear in the "Add widget" panel
Check in order:
CmsFrameworkInstalled::check()returnstrue.class_exists( Livewire\Livewire::class )returnstrue.app( AdminWidgetManager::class )->getAvailableWidgets()returns the three keys.- The current user has the
view_google_search_consolecapability.
The first three are the bridge's responsibility; the fourth is your RBAC layer's.
Cache & configuration
Changed GSC_SITE_URL but the endpoint still queries the old property
Config is cached. Run php artisan config:clear (or php artisan optimize:clear for everything). If you're using config:cache in production, rebuild.
The SearchAnalyticsClient also caches successful responses — a warm cache from before the change will still return the old data. Either wait cache_ttl seconds or forget the keys manually:
php artisan tinker
>>> Redis::keys('google-search-console:query:*')->each(fn($k) => Cache::forget($k))
Two tenants see each other's data
Almost certainly a cache key collision, or the resolver isn't tenant-scoped.
Check:
- Cache keys include
$connection->getKey()— distinctuser_idproduces distinct keys, so this should be safe by default. - Multi-tenant middleware overrides
GoogleConnectionResolver(see API Reference/Support#GoogleConnectionResolver). - No shared connection ID (unlikely — the base package's
google_connections.user_idis unique).
The Livewire component's wire:poll.60s hits the server but the response doesn't update
Either the cache is warm (see above) or the underlying data hasn't updated.
Check:
- Search Console updates once per day at most. Polling at 60s intervals mostly returns the same numbers.
- Lower
cache_ttlif you want faster polling to be useful.
Diagnostic recipes
Dump what the client is sending Google
use Illuminate\Support\Facades\Http;
Http::macro( 'debug', function () {
return $this->beforeSending( function ( $request ) {
logger( 'GSC request', [
'url' => $request->url(),
'headers' => $request->headers(),
'body' => $request->body(),
] );
} );
} );
// Then use ->debug()->post() in a local rebind of SearchAnalyticsClient.
Or use Guzzle's built-in HandlerStack middleware — same effect.
Verify the scope registration end to end
php artisan tinker
>>> app( ArtisanPackUI\Google\Scopes\ScopeRegistry::class )->all()
=> [ ..., "https://www.googleapis.com/auth/webmasters.readonly", ... ]
>>> app( ArtisanPackUI\Google\Facades\Google::class )::oauth()->authorizationUrl( auth()->id() )
=> "https://accounts.google.com/o/oauth2/v2/auth?..."
The URL should include scope=...%20webmasters.readonly....
Confirm the CMS bridge is registering the widgets
php artisan tinker
>>> ArtisanPackUI\GoogleSearchConsole\Support\CmsFrameworkInstalled::check()
=> true
>>> array_keys( app( ArtisanPackUI\CMSFramework\Modules\AdminWidgets\Services\AdminWidgetManager::class )->getAvailableWidgets() )
=> [
"google-search-console.performance-card",
"google-search-console.top-queries-table",
"google-search-console.top-pages-table",
// ... plus whatever other packages contribute
]
Still stuck?
- Testing — every failure mode above is covered by the package's own test suite. If you're not sure whether a behavior is a bug or a design, check
tests/Feature/*for the reference. - FAQ — design rationale for anything that seems weird.
- GitHub issues on
artisanpack-ui/google-search-console— file a repro.