ArtisanPack UI
DocsGoogle Search ConsoleTroubleshooting

Troubleshooting

Google Search Console v1.0.0

Common errors and what to check first. If your problem isn't here, the FAQ covers the "why is this designed like that?" questions. Thrown from or the...

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:

  1. Is artisanpack-ui/google in composer.json? Run composer show artisanpack-ui/google — expect the current version.
  2. Did you run composer install / composer dump-autoload?
  3. In tinker: class_exists( ArtisanPackUI\Google\Google::class ) — should be true. If false, autoloader isn't picking it up.
  4. 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:

  1. GoogleConnection row: status = 'connected'? If not, the user needs to reconnect.
  2. disconnect_reason column: if set, that's the base package's own explanation (typically "Refresh token revoked or expired.").
  3. Did APP_KEY rotate 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:

  1. Is googleapis.com reachable from your app server? curl -v https://searchconsole.googleapis.com/webmasters/v3/sites.
  2. Firewall / proxy blocking outbound HTTPS?
  3. google-search-console.reporting.timeout too 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 siteOwner or siteFullUser permission on the property. siteRestrictedUser isn't enough for searchAnalytics.query.
  • The webmasters.readonly scope 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_URL matches one of the siteUrl values from sites.list verbatim. See Installation/Property Verification.
  • Trailing slashes, http vs https, www prefix 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 SearchAnalyticsClient cache should keep this rare — check google-search-console.reporting.cache_ttl is > 0.
  • If you must, throttle in your job scheduler (RateLimited middleware 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 the disconnect_reason column.
  • Multi-tenant app? The connection is scoped by user_id; if your tenant model doesn't reuse the User model, override GoogleConnectionResolver — 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:

  • hasData public 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 90 or 180 — 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 call fetch() 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:

  1. CmsFrameworkInstalled::check() returns true.
  2. class_exists( Livewire\Livewire::class ) returns true.
  3. app( AdminWidgetManager::class )->getAvailableWidgets() returns the three keys.
  4. The current user has the view_google_search_console capability.

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() — distinct user_id produces 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_id is 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_ttl if 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.