Skip to main content

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"

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.

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 --version on 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.