NAME
PrivatusAnalytics - add the cookieless Privatus Analytics script to Perl web apps
SYNOPSIS
use PrivatusAnalytics qw(event_attrs click_attr);
my $privatus = PrivatusAnalytics->new(
site_id => 'pa_YOURSITEID',
environment => 'production',
);
print $privatus->tag;
# <script defer src="https://privatusstats.com/js/pa.js" data-site="pa_YOURSITEID"></script>
print event_attrs('Report exported', { format => 'csv' });
# data-privatus-event="Report exported" data-privatus-prop-format="csv"
print click_attr('pricing.cta');
# data-privatus-click="pricing.cta"
DESCRIPTION
Adds the cookieless Privatus Analytics tracking script to your Perl web app. There is a plugin for Mojolicious (Mojolicious::Plugin::PrivatusAnalytics), a plugin for Dancer2 (Dancer2::Plugin::PrivatusAnalytics), a Plack middleware for any PSGI app (Plack::Middleware::PrivatusAnalytics), and this plain module you can use anywhere.
Privatus Analytics counts visits, sources, pages and conversions without cookies, local storage or fingerprinting, so analytics does not need a consent banner by itself. Check with your own counsel for your country.
This distribution only prints the browser script and a few HTML attributes. It never sends events from your server and sets no cookies. You need a Privatus Analytics account to see reports, and there is a free plan. Sign up at https://privatusanalytics.com.
REQUIREMENTS
Perl 5.20 or later. Optional: Mojolicious 9 or later, Dancer2 0.400000 or later, or Plack. A Privatus Analytics site id (it starts with pa_). You find it under Site settings, Tracking.
INSTALL
cpanm PrivatusAnalytics
For the full setup guide and every tracker option, see the Privatus Analytics docs.
Set your site id with the PRIVATUS_SITE_ID environment variable, or pass it as the site_id setting.
ADD THE TAG
Mojolicious
plugin PrivatusAnalytics => { site_id => 'pa_YOURSITEID' };
<head>
%= privatus_analytics_tag
</head>
Dancer2
# config.yml
plugins:
PrivatusAnalytics:
site_id: pa_YOURSITEID
use Dancer2::Plugin::PrivatusAnalytics;
<head>
[% privatus_analytics_tag() %]
</head>
Plack (any PSGI app)
builder {
enable 'PrivatusAnalytics', site_id => 'pa_YOURSITEID';
$app;
};
What it prints
In production it prints:
<script defer src="https://privatusstats.com/js/pa.js" data-site="pa_YOURSITEID"></script>
The tag prints nothing when there is no site id, and nothing outside production. If your app uses a Content Security Policy nonce (a random code that lets a script run), pass it as the nonce setting.
Or let it add the tag for you
Set auto_inject => 1 in the Mojolicious or Dancer2 plugin. The Plack middleware always does it. It adds the tag just before </head> in uncompressed text/html responses with status 200. It skips Ajax requests, pages without a <head>, and pages that already have the tag. Responses with a Content-Encoding header are left alone. The Content-Length header is fixed for you.
CONFIGURATION
Every setting is optional except the site id.
- site_id
-
The
data-siteattribute. Falls back to$ENV{PRIVATUS_SITE_ID}. Nothing prints without it. - script_host
-
Where
/js/pa.jsis served. Falls back to$ENV{PRIVATUS_SCRIPT_HOST}, thenhttps://privatusstats.com. A trailing slash is removed. - modules
-
The
data-modulesattribute, for example[qw(engage auto)].undefkeeps the tracker default (engage).[]loads none. - excluded_paths
-
The
data-excludeattribute. Path patterns the tracker never counts. - domains
-
The
data-domainsattribute. Count only these hostnames. - spa
-
auto,history,hashoroff. Only values other thanautoprint. - manual
-
True prints
data-manual="true"and turns off the automatic pageview. - enabled
-
1or0forces the tag on or off in every environment.undefusesenabled_environments. - enabled_environments
-
Environments that get the tag. Default
['production']. The Plack middleware uses['deployment', 'production']. - environment
-
The current environment. The plugins set it from the framework. Otherwise it falls back to
$ENV{PLACK_ENV}, thendevelopment. May be a code ref. - auto_inject
-
Adds the tag to the head of HTML pages. On by default in the Plack middleware.
- extra_attributes
-
Any other
data-*attribute, as a hash ref or an array ref of pairs. - props
-
Page props sent with every pageview and event. A hash ref, an array ref of pairs, or a code ref that gets the request. See "Page props".
- queue_stub
-
True prints a small script first, so
privatus.trackcalls made before the tracker loads are kept and sent later. It still prints when the tag is off (then nothing else prints), so those calls never fail. - nonce
-
The Content Security Policy nonce. A string, or a code ref that gets the request.
Lists can be array refs or comma separated text. Each tracker attribute is explained in the script attributes reference.
Development and test
Development and test requests are not counted by default. The tag is on when the Mojolicious mode is production, the Dancer2 environment is production, or PLACK_ENV is deployment or production for the Plack middleware. To see the tag while you build, set enabled => 1. The tracker also ignores localhost unless you pass data-allow-local or data-debug with extra_attributes.
Leaving people out
Use excluded_paths for admin areas. To leave out signed-in staff, print the helper only for other people.
METHODS
- new(%settings)
-
Makes a settings object. Unknown settings are an error. Every setting has a get and set accessor of the same name.
- tag(%options)
-
The tag HTML. When the tag is off it is only the queue stub (when
queue_stubis on) or an empty string. Takes any setting as a per call override, plusrequest(passed to thepropsandnoncecode refs) andprops(merged over the configured props;undefremoves one). - is_enabled($environment)
-
Whether the tag is on in that environment (default: the current one).
- should_render
-
Whether a tag prints at all: there is a site id and the tag is on.
- page_props($request, $extra)
-
The configured props for one request with
$extramerged on top, as an array ref of pairs. - wants_inject(%response)
-
Used by the auto inject hooks. Takes
status,content_type,content_encodingandrequested_with.
FUNCTIONS
Exported on request (:all for every one).
- event_attrs($name, \%props)
-
Event attributes as escaped HTML. Props can be a hash ref (keys sorted), an array ref of pairs or a list of pairs.
- event_data($name, \%props)
-
The same attributes as a list of pairs.
- click_attr($name)
-
The click map attribute as HTML, or an empty string when the name is not valid.
- click_data($name)
-
The click map attribute as a list of pairs.
- valid_click_name($name)
-
Whether a click map name is valid.
- page_props_json(\%props)
-
Page props as JSON that is safe inside a script tag, or
undef. - inject_tag($html, $tag)
-
Puts the tag before the first
</head>, unless the page already loads/js/pa.js. - html_escape($text)
-
Escapes
& < > " '.
PRODUCT ANALYTICS: SEE WHICH FEATURES PEOPLE USE
Privatus can show which buttons, forms and features people really use. You add a few attributes to your HTML. The tracker reads them in the browser and sends the events. Your server still sends nothing.
Name events well
Use a short "Noun verb" name, like Report exported or Invite sent. Put the details in props, not in the name. So write Report exported with format => 'csv', not Report exported as CSV. Then all exports add up in one place, and you can split them by format.
Events on buttons, links and forms
<button <%= privatus_event 'Report exported', { format => 'csv', source => 'toolbar' } %>>Export</button>
prints
<button data-privatus-event="Report exported" data-privatus-prop-format="csv" data-privatus-prop-source="toolbar">Export</button>
On a <form>, the event is sent when the form is submitted. The rules:
The name is trimmed and cut to 120 characters. An empty name prints nothing.
Prop keys are made lowercase. Anything that is not a letter, a digit or
_becomes_, so"Report Type"becomesreport_type. Keys are cut to 64 characters.At most 30 props. Values become text: numbers as written, lists joined with commas,
\1and\0(orJSON::PP::trueandJSON::PP::false) becometrueandfalse.undefvalues are left out. Values are cut to 500 characters.Everything is escaped for HTML.
The attributes print in every environment, even when the tag is off.
Click maps
A click map shows where people click on a page. Give the important parts a name: <a href="/pricing" <%= privatus_click 'pricing.cta' %>>. A name may use letters, digits and _ . : -, up to 100 characters. Anything else prints nothing.
Page props
Page props are sent with every pageview and event on the page. Use them to split your reports by plan, role, app version, feature flag or account tier.
props => sub { my ($request) = @_; { plan => 'pro', role => 'admin' } }
The tag then prints a small script before the tracker and points the tracker at it:
<script>window.privatusPageProps=function(){return {"plan":"pro","role":"admin"}}</script>
<script defer src="https://privatusstats.com/js/pa.js" data-site="pa_YOURSITEID" data-props="privatusPageProps"></script>
Keys follow the same rules as event props. Numbers and true or false stay as they are, and lists stay lists (up to 30 items). The props are escaped so they are safe inside a script.
Never put names, emails, user ids or anything that points to one person in props. Privatus counts visitors without any id. Put groups (like the plan or the role), not people. The server also removes emails and long ids it finds.
Track from your own JavaScript
privatus.track("Feature used", { feature: "export" })
If your code may run before the tracker has loaded, set queue_stub => 1. It prints this tiny script before the tracker:
<script>window.privatus=window.privatus||{q:[]};['track','pageview','props','optOut','optIn','ready'].forEach(function(m){window.privatus[m]=window.privatus[m]||function(){window.privatus.q.push([m,Array.prototype.slice.call(arguments)])}})</script>
The stub prints even where the tag is off (in development, with enabled => 0, or with no site id). Then only the stub prints, so you can always call privatus.track(...). In development the calls just wait in the queue and are never sent.
See the JavaScript API docs.
See the results
Funnels, Click maps, Goals and Events.
PRIVACY
The script sets no cookies and stores nothing in the browser. This distribution only prints HTML attributes and small scripts. Nothing here reads IP addresses or browser details, and nothing is sent from your server. See the privacy documentation.
SEE ALSO
Mojolicious::Plugin::PrivatusAnalytics, Dancer2::Plugin::PrivatusAnalytics, Plack::Middleware::PrivatusAnalytics, https://privatusanalytics.com/docs
CONTRIBUTING
Bug reports and pull requests are welcome at https://github.com/Privatus-Analytics/perl.
AUTHOR
Two Phase LLC, Wyoming, USA.
LICENSE
MIT. Copyright (c) 2026 Two Phase LLC. See the LICENSE file.