spatie/laravel-analytics
Fetch Google Analytics data in Laravel via a simple facade. Query visitors, pageviews, most visited pages and more for a given period, returning Collections. Easy install, configurable credentials, and ready-to-use methods for common reports.
## Getting Started
### Minimal Setup
1. **Installation**: Add the package via Composer:
```bash
composer require spatie/laravel-analytics
Configuration: Publish the config file and set your ANALYTICS_PROPERTY_ID and service_account_credentials_json in .env:
php artisan vendor:publish --tag="analytics-config"
Update .env:
ANALYTICS_PROPERTY_ID=your-ga4-property-id
Credentials: Download the service account JSON from Google Cloud Console and place it in storage/app/analytics/service-account-credentials.json (ensure it’s excluded from Git).
Grant Permissions: Add the service account email (from the JSON) as an "Analyst" in your GA4 property’s Property Access Management.
First Query: Fetch basic data in a controller or service:
use Spatie\Analytics\Facades\Analytics;
use Spatie\Analytics\Period;
$visitors = Analytics::fetchVisitorsAndPageViews(Period::days(7));
Dashboard Analytics:
$trends = Analytics::fetchVisitorsAndPageViewsByDate(Period::days(30));
transform()):
$chartData = $trends->transform(function ($item) {
return [
'date' => $item['date'],
'visitors' => $item['activeUsers'],
'pageviews' => $item['screenPageViews'],
];
});
Content Performance:
$topPages = Analytics::fetchMostVisitedPages(Period::days(30), 10);
get() with dimensions:
$blogTraffic = Analytics::get(
Period::days(30),
['screenPageViews'],
['pagePath'],
10,
[],
0,
null,
false,
null,
new FilterExpression([
'filter' => new Filter([
'field_name' => 'pagePath',
'string_filter' => new StringFilter([
'match_type' => MatchType::CONTAINS,
'value' => '/blog/',
]),
]),
])
);
Audience Insights:
$devices = Analytics::fetchTopBrowsers(Period::days(30));
$os = Analytics::fetchTopOperatingSystems(Period::days(30));
get():
$userSegments = Analytics::get(
Period::days(30),
['activeUsers'],
['userType', 'customDimension1'], // customDimension1 = "User Role"
5
);
Event Tracking:
$eventData = Analytics::get(
Period::days(7),
['eventCount'],
['eventName'],
10,
[OrderBy::metric('eventCount', false)],
0,
new FilterExpression([
'filter' => new Filter([
'field_name' => 'eventName',
'string_filter' => new StringFilter([
'match_type' => MatchType::EXACT,
'value' => 'form_submitted',
]),
]),
])
);
Caching Strategies:
// Override cache lifetime in config/analytics.php
'cache_lifetime_in_minutes' => 60 * 6, // 6 hours
0 in config).Pagination:
offset and maxResults for large datasets:
$page1 = Analytics::get(Period::days(30), ['screenPageViews'], [], 10, [], 0);
$page2 = Analytics::get(Period::days(30), ['screenPageViews'], [], 10, [], 10);
Credentials Security:
service-account-credentials.json to version control. Add it to .gitignore:
storage/app/analytics/service-account-credentials.json
GA4 Property ID:
ANALYTICS_PROPERTY_ID matches the GA4 property ID (not Universal Analytics). Format: properties/12345678.Quota Limits:
quotaExceeded errors.Date Ranges:
$period = Period::create(
Carbon::now()->timezone('UTC')->startOfDay()->subDays(7),
Carbon::now()->timezone('UTC')->endOfDay()
);
Period::allTime()) to prevent quota spikes.Dimension/Metric Combinations:
maxResults (e.g., 5) first.Invalid dimension/metric combination → Check GA4 API docs for valid pairs.Caching Quirks:
php artisan cache:clear
cache_lifetime_in_minutes = 0) to test live data.Filter Complexity:
AND/OR logic) require multiple FilterExpression objects. Use the FilterExpression::combine() method:
$filter1 = new FilterExpression([/* ... */]);
$filter2 = new FilterExpression([/* ... */]);
$combinedFilter = FilterExpression::combine($filter1, $filter2, 'AND');
Enable Debugging:
GOOGLE_APPLICATION_CREDENTIALS env variable to the JSON path for verbose errors:
GOOGLE_APPLICATION_CREDENTIALS=storage/app/analytics/service-account-credentials.json
Google\ApiCore\ApiException details.Validate Responses:
dd():
$response = Analytics::get(/* ... */);
dd($response->toArray());
rowCount, metadata, and errors in the response.Common Errors & Fixes:
| Error | Solution |
|---|---|
Invalid credentials |
Regenerate JSON key in Google Cloud Console. |
Property not found |
Verify ANALYTICS_PROPERTY_ID format and permissions. |
Quota exceeded |
Reduce query frequency or upgrade quota in Google Cloud Console. |
Invalid dimension/metric |
Cross-reference with GA4 API schema. |
Cache miss but no data returned |
Check if the period is valid (e.g., not in the future). |
get() method for unsupported metrics by using the underlying GoogleAnalyticsDataClient:
use Google\Analytics\Data\V1beta\RunReportRequest;
use Google\Analytics\Data\V1beta\Dimension;
use Google\Analytics\Data\V1beta\Metric;
$client = app(\Spatie\Analytics\AnalyticsService::class)->client();
$request = new RunReportRequest([
'property' => 'properties/' . config('analytics.property_id'),
'dimensions' => [new Dimension(name: 'customDimension1')],
'metrics' => [new Metric(name: 'customMetric1')],
'dateRanges'
How can I help you explore Laravel packages today?