Release annotations
An annotation is a note on your Matomo reports timeline. The most useful thing to put there is a deployment marker, because it turns "traffic dropped on the 14th" into "traffic dropped when we shipped 1.4.0".
Matomo's Annotations plugin is free core. It needs a token_auth belonging to a
non-anonymous user, and every call here goes through the same
resilience layer as tracking — so a failed annotation never
breaks a deploy.
From code
use MatomoAnalytics\Facades\MatomoAnnotations;
MatomoAnnotations::add('Migrated to PostgreSQL 16', date: '2026-07-03', starred: true);
MatomoAnnotations::annotateRelease('1.4.0'); // "Deployed 1.4.0"
MatomoAnnotations::annotateRelease('1.4.0', site: 2);
add() takes the note, an optional date (YYYY-MM-DD, defaulting to today), whether to
star it, and an optional site id. It returns the created annotation, or null on failure
with the reason in lastError() — the same convention as
reporting.
"Today" is the day in your app.timezone, the one today() returns. Matomo keeps the date
as it arrives and draws the marker on that day of the site's timeline. If your Matomo site
keeps another time zone, pass the date on its calendar; from a deploy script, for example,
--date="$(TZ=Europe/Berlin date +%F)".
Starring matters more than it sounds: Matomo shows starred annotations more prominently on the timeline, so starring releases and leaving routine notes unstarred keeps the timeline readable.
From a deploy pipeline
php artisan matomo:annotate "Maintenance window"
php artisan matomo:annotate --release # "<prefix> <version>"
'annotations' => [
'release' => env('MATOMO_ANNOTATE_RELEASES', false),
'starred' => false,
'release_prefix' => 'Deployed',
],
matomo:annotate --release is a no-op unless you opt in with annotations.release
(MATOMO_ANNOTATE_RELEASES=true). It prints that it is disabled and exits successfully,
so you can drop it unconditionally into every deploy — including staging, where you
probably do not want markers — and enable it only where it should fire.
It also needs a token_auth (MATOMO_TOKEN) for a non-anonymous user, so it stays inert
in an environment that has none.
Where the version comes from
This is the one part with a real trap in it.
The version comes from --app-version. Without that flag the command falls back to
config('app.version') — which stock Laravel does not define. So either pass it
explicitly:
php artisan matomo:annotate --release --app-version=1.4.0 # posts "Deployed 1.4.0"
…or expose an app version once, and plain --release picks it up:
// config/app.php
'version' => env('APP_VERSION'),
APP_VERSION=1.4.0
If no version resolves at all, the note is just the prefix — Deployed — which is a
usable marker but tells you nothing about what was deployed. That silent degradation is
why this is worth getting right once.
The flag is
--app-version, not--version. Symfony reserves--versionon every Artisan command, so it could not be used here.
The other options
php artisan matomo:annotate "Note" --date=2026-07-01 --starred --site=2
--date sets the annotation date, --starred stars this one note regardless of the
config default, and --site targets a site other than the configured one.
In tests
MatomoAnnotations::fake() records annotations and can be made to fail, so a deploy hook
can be tested without a token. See testing.