tightenco/nova-google-analytics
A Laravel Nova card that surfaces Google Analytics data—sessions, users, page views, and top referrers—right in your Nova dashboard. Connect a service account, configure the metrics you need, and track site performance at a glance without leaving the admin panel.
Prerequisites before you touch Composer — this package is inert without all four:
UA-XXXXX view ID will not work; you need the numeric property ID.Install and wire it up.
composer require tightenco/nova-google-analytics:^4.0
php artisan vendor:publish --provider="Tighten\NovaGoogleAnalytics\NovaGoogleAnalyticsServiceProvider"
Check vendor/tightenco/nova-google-analytics/ for the exact publish tag and config filename — v4 config keys differ from v3, so read the published file rather than copying a v3 config from an older project.
Store credentials as a file path, not inline JSON:
ANALYTICS_PROPERTY_ID=123456789
ANALYTICS_CREDENTIALS_PATH=/run/secrets/ga-service-account.json
The service account JSON is materialised at deploy time from your secret manager. Do not paste raw JSON into .env — the private key's newlines will be mangled and you'll get an opaque auth failure.
First use case — one card, one dashboard, reconciled against the GA4 UI. Register the card in app/Nova/Dashboards/Main.php (or a dedicated dashboard, see below):
// Verify the exact card class name in vendor/ — it's the only card shipped.
use Tighten\NovaGoogleAnalytics\GoogleAnalyticsCard;
public function cards(): array
{
return [
(new GoogleAnalyticsCard)->width('1/2'),
];
}
Then open the dashboard and immediately open the GA4 UI side by side with the same date range. Your first job is not "does it render" — it's "do the numbers match". If they don't, you're pointed at the wrong property or the service account has no grant, and the card will happily show you zeroes either way.
Dedicate a dashboard rather than crowding MainDashboard. This is the single highest-leverage pattern. It keeps analytics off the critical path of your operational dashboard, gives you one file to delete if the package dies, and lets you gate the whole thing with a policy instead of per-card canSee logic.
// app/Nova/Dashboards/AnalyticsDashboard.php
class AnalyticsDashboard extends Dashboard
{
public function cards(): array
{
return [
(new GoogleAnalyticsCard('Traffic — 30 days'))
->width('1/2'),
(new GoogleAnalyticsCard('Acquisition — 30 days'))
->width('1/2'),
];
}
public function uriKey(): string { return 'analytics'; }
}
Multiple cards are the real feature — use them for comparisons, not decoration. Two or three cards configured against different date ranges (this month vs. last month) or different properties (marketing site vs. docs) is where this package earns its keep. Beyond three cards you're rebuilding the GA4 UI badly and burning quota doing it.
Gate by role with a policy, not canSee sprinkled per card. Marketing/ops should see it; your support agents almost certainly shouldn't, and gating at the dashboard level means one place to audit.
// NovaServiceProvider::dashboards()
->dashboards([
new MainDashboard,
(new AnalyticsDashboard)->canSee(fn ($request) => $request->user()->hasRole('marketing')),
])
Reconcile before you promote, every time. Pick two or three date ranges — including one that's > 30 days old, where GA4 data has fully settled — and compare card output against the GA4 UI. Do it once as a non-admin role too, so you catch permission-scoped rendering issues in staging rather than in a stakeholder's first impression.
Wrap it in an off switch. Because the card depends on an external API you don't control, put the card registration behind a config flag so you can disable it without a deploy:
return config('analytics.enabled') ? [(new GoogleAnalyticsCard)] : [];
Use a dedicated property per environment. Point staging at a staging GA4 property, not production. Otherwise every QA pass pollutes your production reporting — or worse, your team reads staging numbers as real.
Treat the service-account key as a rotating secret with a runbook. Who owns the Google Cloud project, when the key rotates, and whether rotation needs a redeploy are questions you want answered in a doc, not discovered during an incident. If your deployment materialises secrets at boot, rotation is a restart.
Zeroes and errors look identical. A missing Viewer grant on the property, a bad property ID, or a disabled Data API all surface as a card that renders but shows nothing useful. Before you file a bug against the package, test the credential outside Nova — a quick call against the GA4 Data API with the same service account tells you instantly whether the problem is Google-side or Nova-side. This single check will save you an afternoon.
v4 is a breaking major, not a patch bump. Metric and dimension names are GA4-style (sessions, activeUsers, screenPageViews) not Universal Analytics style (ga:sessions). Config keys, credential shape, and identifiers all changed. If you're upgrading an existing v3 install, diff the published config rather than assuming it carries over, and land it after your Laravel 11 upgrade — bundling two breaking changes obscures which one broke the dashboard.
The property ID is numeric and not the UA- ID. Sounds trivial; it is the most common misconfiguration. Grab it from GA4 Admin → Property Settings.
GA4 data is not real-time and not immediately complete. Anything in the last 24–48 hours can shift retroactively. Don't build expectations — or tests — around today's numbers, and tell stakeholders the card is a convenience view, not a live console.
Cache aggressively and know what the cache key covers. The card caches responses to limit API calls, but quota is per-property and token-based, and every admin refreshing the dashboard consumes it. If you run multiple cards with different configs, confirm the cache keys differentiate them — otherwise your "last 7 days" card can poison your "last 30 days" card. Verify against your actual deployment, not the description.
Verify Composer constraints against your Nova major before committing. The package's Nova constraint is the one thing most likely to bite you, and it's a two-minute check in composer.json in the vendor directory.
Pin the version. 166 stars, zero dependents, MIT, a last release in mid-2024, and a major shipped by two first-time contributors. This is a Tighten-maintained open-source package with no SLA — budget for the realistic possibility that a future GA4 API change lands before a patch does. Adopt it as "a card we could fork if needed", not as infrastructure.
Document metric definitions outside the repository. When a future major silently changes what "users" means, the only defence is a note in your internal wiki saying which numbers the team expects to see and how they were reconciled. Keep the vendor source as a fallback reference, not the spec.
Know the escape hatch. Funnels, cohorts, attribution, custom-event exploration, multi-property aggregation — all of these are a signal to escalate to a BI decision, not to stretch this card. Naming that boundary before someone asks keeps a "small dashboard request" from quietly becoming a platform commitment.
How can I help you explore Laravel packages today?