Skip to content

Twig API

Everything is under craft.craftAnalytics. It's all read-only and all aggregate - there is no method that can return anything about an individual, because there's nothing about an individual to return. You can't accidentally leak what nobody's holding.

Most read entries

The one everybody wants:

twig
<h2>Most read this month</h2>
<ul>
    {% for entry in craft.craftAnalytics.popularEntries(5).all() %}
        <li>
            <a href="{{ entry.url }}">{{ entry.title }}</a>
            <span>{{ craft.craftAnalytics.views(entry)|number_format }} views</span>
        </li>
    {% endfor %}
</ul>

popularEntries() gives you an entry query, not an array, so carry on refining it:

twig
{# Most read blog posts of the last week #}
{% set popular = craft.craftAnalytics.popularEntries(5, '7d')
    .section('blog')
    .all() %}

It stays in view order through .all(). Adding .section() filters the list without silently re-sorting it by post date, which is the thing you'd want and the thing that usually doesn't happen.

Signature: popularEntries(limit = 5, period = '30d', siteId = null)

Periods are today, yesterday, 7d, 30d, 90d, 12mo.

TIP

It over-fetches internally, because some of the most-viewed elements will since have been unpublished. Asking for 5 gives you 5 where 5 exist, rather than 3 with no explanation.

Views for one entry

twig
{{ craft.craftAnalytics.views(entry)|number_format }} views
{{ craft.craftAnalytics.views(entry, '7d') }} views this week
{{ craft.craftAnalytics.views(1234) }}  {# by ID #}

Site totals

twig
{% set stats = craft.craftAnalytics.totals() %}

{{ stats.views|number_format }} views
{{ stats.uniques|number_format }} visitors
{{ stats.sessions|number_format }} visits
{{ stats.bounceRate|number_format(1) }}% bounced

Available: views, uniques, sessions, bounceRate, avgDurationMs, avgViewsPerSession, avgDwellMs.

Signature: totals(siteId = null, period = '30d')

WARNING

uniques is on a daily-unique basis - somebody who visits on three days counts three times - and it's an estimate, accurate to about 1.6%. Both are deliberate. See How visitors are counted if you're about to put this number somewhere it matters.

When you want paths rather than elements:

twig
{% for page in craft.craftAnalytics.popularPages(10, '7d') %}
    {{ page.path }} - {{ page.views }} views
{% endfor %}

Each row has path, views, elementId, entrances, exits, bounces, bounceRate, avgDwellMs.

Has this visitor opted out?

For suppressing your own consent banner:

twig
{% if not craft.craftAnalytics.gpcDetected %}
    {% include '_partials/cookie-banner' %}
{% endif %}

gpcDetected is true when the browser sent Sec-GPC: 1. Those visitors are never tracked beyond the anonymous tier no matter what any consent tool says, so there's nothing to ask them.

Custom events from JavaScript

js
craftAnalytics.event('newsletter-signup');
craftAnalytics.event('quote-requested', { value: 250 });

Then make a goal of type Event with target newsletter-signup.

Custom events from PHP

For things that happen on a POST, where there's no pageview to hang them off:

php
use coyshdigital\craftanalytics\Plugin;

Plugin::getInstance()->getCapture()->trackEvent('quote-requested', 250.0);

Returns false if nothing was recorded, which is the normal answer on Lite, with events off, or for a visitor sending GPC. It respects every privacy rule the rest of the plugin does - your code can't opt someone in.

Caching your own analytics-driven markup

If you're rendering "most read" on every page load, cache it. The queries are cheap but they're not free, and this changes about as often as you'd expect:

twig
{% cache for 1 hour %}
    {% for entry in craft.craftAnalytics.popularEntries(5).all() %}

    {% endfor %}
{% endcache %}

Digital help you can trust.