rocramer/nova-matomo-analytics
This Laravel Nova card integrates Matomo analytics directly into your admin panel, letting you display visitors, page views, and other key metrics with configurable date ranges and widgets for quick at-a-glance insights into your site traffic.
Prerequisites. This package is worthless without both halves of the bridge: a working Laravel Nova install (paid, closed-source, and the actual compatibility risk) and a reachable Matomo instance you can query. Get the Matomo side working before touching Composer — it isolates credential problems from code problems.
Step 0 — prove the Matomo API works with curl. Do this first, from the same host that will run Nova:
curl -s "https://matomo.example.com/index.php" \
--data-urlencode "module=API" \
--data-urlencode "method=VisitSummary.get" \
--data-urlencode "idSite=1" \
--data-urlencode "period=day" \
--data-urlencode "date=today" \
--data-urlencode "format=json" \
--data-urlencode "token_auth=${MATOMO_TOKEN}" | jq
If this returns numbers, every later failure is a Nova/config problem. If it returns an HTML login page or {"result":"error"}, you have a token scope, framing, or URL problem and no amount of PHP debugging will help.
Create a dedicated Matomo user with view access to only the site(s) you need, and generate the token under that user. Do not reuse an admin token.
Step 1 — install and pin.
composer require rocramer/nova-matomo-analytics:0.5.0
Pin the exact version. Last release was April 2021, pre-1.0, 22 stars, zero dependents — ^0.5 buys you nothing but surprise.
Step 2 — publish config and assets.
php artisan vendor:publish --tag=nova-matomo-analytics-config
php artisan nova:publish
Confirm the provider/tag names against the package's composer.json and src/ — the README is thin and the exact publish tag is the kind of thing that silently no-ops.
Step 3 — read the source before you configure. The published description says "queries the Matomo Reporting API and renders key metrics as cards," while the v0.5.0 changelog describes card aggregation changes ("sum value instead of the latest value", avg_time_generation rename) — that's a Reporting API client shape, not an iframe. But the PM/TPM notes flag iframe embedding as a live possibility. Open src/ and the compiled Vue assets and answer one question: is this a server-side HTTP client against index.php?module=API, or an <iframe>/widget embed? It determines your CSP work, your token exposure surface, and your failure modes. Ten minutes here saves a bad security review.
Step 4 — configure. Fill the published config with the Matomo base URL (no trailing slash), the numeric idSite, and the token. Route values through env() so per-environment credentials work and config:cache stays honest.
First use case: one card — visitors today for your primary site — registered on a throwaway dashboard, viewed by you and nobody else. Do not start with a full traffic panel.
Register on a dedicated dashboard, not the main one. Every card is a blocking call to an external service during dashboard render. Six cards on Main means six round-trips before an admin sees their home screen — and Matomo latency becomes Nova latency.
// app/Providers/NovaServiceProvider.php
protected function dashboards()
{
return [
(new \App\Nova\Dashboards\Analytics)->cards([
\App\Nova\Cards\VisitorsToday::make()->width('1/3'),
\App\Nova\Cards\PageviewsThisWeek::make()->width('1/3'),
]),
];
}
Nova 3 uses a flat cards() array instead; check which major you're on before copying snippets from any README.
Subclass, never edit vendor. Wrap each card in your own class in app/Nova/Cards/:
class VisitorsToday extends \Rocramer\NovaMatomoAnalytics\Cards\VisitorsCard
{
public function ranges() { /* trim to ranges Matomo can actually serve */ }
public function canSee(Request $request) { return $request->user()->can('view analytics'); }
public function uriKey() { return 'matomo-visitors-today'; }
}
This is your whole extension strategy. It keeps composer update and a future fork merge viable, and it lets you swap canSee(), ranges(), and caching without fighting the base class.
Authorization is yours to add. Assume the card does not honor Nova's per-resource permission model — dashboard visibility almost certainly equals analytics visibility. canSee() on each card is the minimum; a Nova policy or gate is better, and it's the one thing you should not ship without.
Suppress on local/staging.
public function canSee(Request $request): bool
{
return app()->isProduction() && $request->user()->can('view analytics');
}
Otherwise every developer's php artisan serve fires live API calls at production Matomo and burns your API quota.
Cache aggressively in a subclass. Traffic numbers do not need to be realtime. If the card issues an uncached request per render, add Cache::remember("matomo.visitors.{$siteId}", now()->addMinutes(10), fn () => ...) around the data fetch. Ten minutes of staleness is invisible to a human glancing at a card and removes an entire class of page-load latency.
Multi-site means duplicated cards. The package renders Matomo's views; it doesn't compose new ones. One card per idSite, each with its own subclass or config key. If someone asks for a consolidated cross-brand view, that's a first-party project, not a config change — say so early.
Trim the date ranges. 0.5.0 added 180- and 365-day ranges. If Matomo's retention only keeps ~180 days of raw data (or your archive/browser_archiving_disabled settings differ), those long ranges silently return partial or aggregated numbers under a label that implies completeness. Override ranges() to what your Matomo instance can actually answer, and verify the long ranges against the Matomo UI side by side before shipping them.
If it's an iframe, the blank rectangle is your problem. frame-src in your CSP must allow the Matomo origin, Matomo must permit framing, and there must be no mixed content (HTTPS Nova → HTTP Matomo). None of these are package settings, all of them surface as an empty card with no error, and all of them are invisible until a user reports "the box is empty."
Grep the built JS for token_auth. If the token reaches the browser — via a card prop, meta(), or a signed widget URL — every Nova user can read it in devtools. It should live server-side and never appear in the compiled assets. Run grep -r token_auth public/vendor/nova* resources/js and be satisfied with the answer.
Test the Matomo-down path deliberately. Point the config at a bad hostname and load the dashboard. If it throws, the analytics card takes down the admin home page — wrap the fetch and degrade to a zero/error state. A broken card should look broken, not 500 the dashboard.
v0.5.0 silently redefined the numbers. "Show sum value instead of the latest value" changes what a card means under an unchanged label, and avg_time_generation was renamed. If you upgraded, or if anyone is comparing a screenshot from before, the numbers moved without the title changing. Pin the version and write down what each card actually computes before you show it to stakeholders who will cite it in a meeting.
Timezone drift shows up as off-by-one days. Matomo's date=today resolves in the site's timezone; Nova/PHP may be on UTC. Near midnight you'll see yesterday's traffic in a "Today" card. Set the Matomo site timezone deliberately and document it, because this is the #1 source of "this number looks wrong" tickets.
Stale credentials are almost always config:cache. Changing .env without php artisan config:clear gives you authentication errors that look like Matomo rejecting a valid token. Clear the cache before you debug anything else.
Blank card after install is usually registration, not the package. Check that the card is in the right cards()/dashboards() method for your Nova major, and that nova:publish actually copied the compiled assets. Nova caches component manifests; a hard refresh plus an asset republish fixes most of it.
Assume you are the maintainer. Four-plus years since the last release against a fast-moving closed-source host. Budget a half-day per Nova upgrade cycle to re-validate, keep the card registration in a single file so rollback is one commit, and treat "we'll fork it" as already true rather than a contingency. MIT makes that viable; nothing else does.
Rotate the token on a schedule. It sits in an admin panel's config and probably in database backups. A view-scoped token for a dedicated Matomo user limits the blast radius; rotation limits the lifetime.
How can I help you explore Laravel packages today?