Odometer for Craft CMS

Troubleshooting

Nothing is being counted

Check the mode. Settings → Odometer → How views are counted. If it is Only where asked or Not at all, that is the answer.

Check that the page resolves to an element. Automatic counting needs Craft to have matched a URL to an element. A template rendered from a route, a category listing, a search results page and the homepage of most sites are not elements, so nothing is counted on them. Put {% do craft.odometer.record(entry) %} where you want a specific thing counted.

Check the section scope. Settings → Odometer → Sections.

Check that you are not being ignored. By default admins are not counted, so testing while signed in as yourself shows nothing. Sign out, use a private window, or set Signed-in users to Count everybody while you test.

Check the dedupe window. After the first view, the same browser will not count again for thirty minutes. Set it to 0 while testing.

Check the robot list. curl is on it, and so is wget — a curl request will not be counted no matter what else is right. Pass a browser user agent when testing from the command line:

curl -A 'Mozilla/5.0 (Macintosh) Chrome/126.0' https://example.test/some-page

Counts stopped a while ago and I only just noticed

Almost always a page cache that went in since the counter did. Craft renders each page once and never hears about the readers who get the cached copy, so the count does not stop dead — it drops to the rate of cache misses, which is why nobody notices for weeks.

Switch to beacon mode.

The beacon is in the page but nothing is counted

Fetch the beacon URL yourself and look at what comes back:

curl -si -A 'Mozilla/5.0 Chrome/126.0' 'https://example.test/actions/odometer/track/beacon?odometerToken=…'

It always answers 200 with a 42-byte GIF, whatever happened — a beacon that returned 403 to a robot would be telling the robot how to look like a person. So the status tells you nothing; the count does.

If the count does not move:

  • The token is being mangled. It is URL-safe base64; check that nothing between the page and Craft is rewriting query strings, and that the & in the URL has not been HTML-escaped into & by something copying it by hand.
  • The security key changed. Tokens are signed with it, so every token minted under the old key is refused. New page views mint new tokens; old cached pages carry dead ones. Clear the page cache.
  • The token expired. Only possible if Token lifetime was changed from 0. On a cached site the token lives as long as the cached HTML does.
  • The request is being filtered. Robots, ignored addresses, Do Not Track, signed-in users, the dedupe window — in that order.

The whole site went down and Odometer is in the stack trace

It should not be possible: counting catches its own errors and warns rather than throwing, and the response filter does the same. If you have a trace that says otherwise it is a bug worth reporting — but first switch How views are counted to Not at all, which stops every code path except reading.

An element index throws when I sort by Views

Report it. Sorting by Views works whether or not the Views column is shown, because the join is added by a handler on ElementQuery::EVENT_BEFORE_PREPARE when the sort names it. If that has been defeated somehow, the workaround is to add the Views column to the source's columns as well.

popular() returns fewer rows than I asked for

By design: elements that have never been read are not in the ranking at all, because nothing unread can be the most-read thing. Pass includeUnviewed: true to keep them, at the bottom.

popular() returns the wrong rows

If it is returning the right number of rows in the right order but they are not the top ones, the ranking has been applied to a query that was already limited. Apply the ranking first, or pass limit in the options rather than calling .limit() beforehand.

The report screen is slow

The report ranks straight out of the ledger and then asks Craft for the elements behind the ids — one query for the ranking, one per element type for the elements, one for the lifetime numbers and one for all the sparklines. If it is slow, the ledger is probably very large; turn retention down and prune:

php craft odometer/views/prune

Retention only ever deletes day buckets. Lifetime totals are never touched.

Numbers look wrong after a restore or a migration

php craft odometer/views/recount rebuilds every lifetime total from the day buckets. On a site with retention switched on this replaces lifetime totals with "views since the retention window began", so it asks before doing it. Set retention to 0 first if you want to be sure.

The dates are a day out

Day buckets are calendar days in the site's time zone (Settings → General → Time Zone), not UTC. If Craft's time zone is not where your readers are, "today" will not match what they would call today. There is no setting for this: Craft has one system time zone and Odometer uses it.

Views appear on the wrong site

Every count is per site. A page read in French counts for the French site. Pass siteId: '*' wherever a site is accepted to add them up.

Where do I look next

Craft's own logs, filtered to Odometer:

grep odometer storage/logs/web-*.log

Odometer logs at warning level when a view could not be recorded and at error level when the response filter fell over. Both are non-fatal by design, so a problem here is quiet unless you go looking.