Self-hosted mouse, click and scroll tracker for Symfony 5.4 / 6.4 / 7.x. Captures mousemoves, clicks, scroll positions, form-blur values and DOM snapshots, then ships them to your own database for Mouseflow-style heatmaps and session replay. No jQuery.
| Package | Version |
|---|---|
| PHP | >=8.1 |
| Symfony | ^5.4 | ^6.4 | ^7.0 |
| Doctrine ORM | ^2.14 | ^3.0 |
composer require benmacha/mousetracker:^2.0If you don't use Symfony Flex, register the bundle in config/bundles.php:
return [
// ...benmacha\mousetracker\TrackerBundle::class => ['all' => true],
];config/routes/mouse_tracker.yaml:
mouse_tracker:
resource: '@TrackerBundle/Resources/config/routes.yaml'prefix: /trackerconfig/packages/mouse_tracker.yaml:
mouse_tracker:
record_click: truerecord_move: truerecord_keyboard: truepercentage_recorded: 100disable_mobile: falseignore_ips: []php bin/console doctrine:schema:update --force
# or, with migrations:
php bin/console doctrine:migrations:diff
php bin/console doctrine:migrations:migrateThis creates three tables: tracker__client, tracker__page, tracker__data.
php bin/console assets:install --symlink public/Copies the tracker JavaScript to public/bundles/tracker/js/tracker.js.
Inject the tracker snippet right before the closing </body> tag of any page you want to record:
{# templates/base.html.twig #}
{{ mouse_tracker_service.build()|raw }}
</body>
</html>Expose the service as a Twig global in config/packages/twig.yaml:
twig:
globals:
mouse_tracker_service: '@mouse_tracker'That's it — the tracker boots on DOMContentLoaded and starts batching events to /tracker/createClient and /tracker/addData.
The bundle ships a basic backend at /tracker/back/ for listing sessions and replaying recordings.
⚠️ Security — read this before deploying. The/tracker/back/*routes (replay UI +getPages/getClients/getDataJSON endpoints) are public by default. Anyone who can reach your app can browse every recorded session, including form values, click coordinates, and DOM snapshots — which often contain PII.Gate them behind your
security.yamlfirewall before you ship. Example:# config/packages/security.yamlsecurity: access_control: - { path: ^/tracker/back, roles: ROLE_ADMIN }# the ingest endpoints below must stay reachable from the public site - { path: ^/tracker, roles: PUBLIC_ACCESS }The same goes for GDPR/consent:
/tracker/createClientand/tracker/addDataare intentionally public so the tracker can post from any page, but you are responsible for getting visitor consent before loadingtracker.js.
Client-side settings are forwarded from PHP config into a window.MouseTrackerConfig.settings blob. You can also override them after the script loads via UST.settings:
<script>window.UST&&(UST.settings.delay=100);</script>| Key | Default | Description |
|---|---|---|
record_click | true | Record left/right clicks |
record_move | true | Record mousemove heatmap data |
record_keyboard | true | Capture input/textarea values on blur (never password) |
percentage_recorded | 100 | % of visitors to sample |
disable_mobile | false | Skip recording on mobile UAs |
ignore_ips | [] | IP allowlist via l2.io/ip.js (opt-in) |
v2 is a breaking rewrite. See CHANGELOG.md for the migration path. Highlights:
- Drops Symfony 2.8, Assetic, Sensio Framework Extra Bundle, jQuery
- Doctrine/route annotations replaced with PHP 8 attributes
services.yml→services.yaml; newroutes.yamlresourcemousetrackerServiceglobal →mouse_tracker_service- Tracker JS is now plain vanilla — no jQuery to load first
MIT — see Resources/meta/LICENSE.