PHPackages                             zhortein/seo-tracking-bundle - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. zhortein/seo-tracking-bundle

ActiveSymfony-bundle[Utility &amp; Helpers](/categories/utility)

zhortein/seo-tracking-bundle
============================

This bundle is dedicated to Symfony applications and provide metrics gathering on page calls.

1.2.8(2mo ago)043MITPHPPHP &gt;=8.3

Since Jun 30Pushed 2mo agoCompare

[ Source](https://github.com/Zhortein/seo-tracking-bundle)[ Packagist](https://packagist.org/packages/zhortein/seo-tracking-bundle)[ Docs](https://www.david-renard.fr)[ RSS](/packages/zhortein-seo-tracking-bundle/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (10)Dependencies (20)Versions (13)Used By (0)

SEO Tracking Bundle
===================

[](#seo-tracking-bundle)

Symfony bundle to track page views, UTM campaigns and basic engagement, with optional SEO insights integration.

[![Latest Version on Packagist](https://camo.githubusercontent.com/ee44113a3c24f7240e6ae731bec5b2cdfcb4075cb153205bbee6a0393984d8d8/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f7a686f727465696e2f73656f2d747261636b696e672d62756e646c652e737667)](https://packagist.org/packages/zhortein/seo-tracking-bundle)[![Total Downloads](https://camo.githubusercontent.com/a583f9f2c2f7fab0cf0992c33a72af6c0f84a43d63df810c8b481fd1b025fbb3/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f7a686f727465696e2f73656f2d747261636b696e672d62756e646c652e737667)](https://packagist.org/packages/zhortein/seo-tracking-bundle)[![License](https://camo.githubusercontent.com/e19c10a1166c672840366a1660ad28bd4ee86f71b9daf219563e4cedd2a38a88/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f6c2f7a686f727465696e2f73656f2d747261636b696e672d62756e646c652e737667)](https://github.com/Zhortein/seo-tracking-bundle/blob/main/LICENSE)

📦 Installation
--------------

[](#-installation)

```
composer require zhortein/seo-tracking-bundle
```

Symfony 6.3+ and 7.x are fully supported.

If you're not using Symfony Flex, enable the bundle manually in config/bundles.php:

```
Zhortein\SeoTrackingBundle\SeoTrackingBundle::class => ['all' => true],
```

⚠️ Database migration required!
-------------------------------

[](#️-database-migration-required)

After installing the bundle, and sometimes upgrading the bundle (check the [CHANGELOG](./CHANGELOG.md)), you must run a migration to create the required database tables:

```
php bin/console make:migration
php bin/console doctrine:migrations:migrate
```

If you're using custom naming strategies or a prefixed schema, review the generated migration before applying it.

⚙️ Usage
--------

[](#️-usage)

To enable tracking, include the [Stimulus](https://stimulus.hotwired.dev/) controller in your layout or any page you want to track.

Add the following to your `` tag to enable tracking:

```

```

We recommend placing the tracking call as early as possible in your page. The `` tag is a good place for this.

The `type` value is optional and allows you to define the nature of the page (e.g. `home`, `contact`, `form`, etc.).
This helps categorize traffic for SEO or UX analytics purposes.

### 🔧 Simplified usage with Twig helper

[](#-simplified-usage-with-twig-helper)

Instead of writing the `stimulus_controller(...)` call manually, you can use the built-in Twig function:

```

```

This will generate:

```
  data-controller="zhortein--seo-tracking-bundle--tracking"
  data-zhortein--seo-tracking-bundle--tracking-route-value="app_home"
  data-zhortein--seo-tracking-bundle--tracking-route-args-value="[]"
  data-zhortein--seo-tracking-bundle--tracking-type-value="home"
```

The function automatically injects the current Symfony route and route parameters.

> ✅ You can safely place this
>
>  in your layout or any tracked template. ⚠️ Do not use both `stimulus_controller(...)` and `seo_tracking(...)` at the same time.

🧠 What does this bundle track?
------------------------------

[](#-what-does-this-bundle-track)

The bundle automatically collects basic visit tracking data using Stimulus and `fetch()` calls, without setting cookies or requiring consent (GDPR-friendly by default). Data is sent asynchronously when a page is loaded and just before the user exits the page.

Tracked data includes:

- 📄 Current URL
- 🔀 Symfony route and route arguments
- 📈 UTM campaign data (from URL)
- 🌐 Browser language (navigator.language)
- 🖥️ Screen size (screen.width and screen.height)
- ⏱️ Entry and exit timestamps (tracked via JS)

⚙️ How it works
---------------

[](#️-how-it-works)

1. On page load, a fetch() request is sent to the tracking endpoint.
2. The server stores a new PageCall and a new PageCallHit.
3. A listener is added to the page to detect page exit.
4. On page unload (tab close, navigation), a fetch() request is sent to update the exitedAt timestamp and calculate the duration.

⚠️ Notes &amp; Best Practices
-----------------------------

[](#️-notes--best-practices)

- Only include the stimulus\_controller call once per page (usually in your base layout).
- The bundle does not store any cookies or personal identifiers.
- Works well in static pages, Turbo/Stimulus navigation or multi-page apps.
- Fully GDPR-compliant by design (but double-check based on your legal context).

📐 Data model overview
---------------------

[](#-data-model-overview)

> PageCall groups traffic based on UTM parameters and URL.
> PageCallHit represents each individual visit or hit within that group.

### PageCall

[](#pagecall)

This entity stores Page calls grouped by their UTM values, with counting calls. It's related to a collection of hits.

- url: URL called
- route: Symfony route called
- routeArgs : Arguments for the called Symfony Route
- campaign: utm\_campaign argument received
- medium: utm\_medium argument received
- source: utm\_source argument received
- term: utm\_term argument received
- content: utm\_content argument received
- nbCalls: Number of calls with the UTM context
- lastCalledAt: datetime of the last call
- firstCalledAt: datetime of the first call
- hits: related PageCallHits (see below)
- bot: true if this call was made by a bot

### PageCallHit

[](#pagecallhit)

This entity stores information related to a visit (hit) and is related to a PageCall:

- pageCall: related PageCall
- referrer: URL of the referrer
- userAgent: received User Agent, raw format
- anonymizedIP: IP address of the visitor anonymized (GDPR compliance)
- calledAt: datetime of the call
- exitedAt: datetime of page exit
- durationSeconds: calculated duration of the visit
- language: navigator language (if provided by the navigator)
- screenWidth: screen width in pixels (if provided by the navigator)
- screenHeight: screen height in pixels (if provided by the navigator)
- parentHit: previous PageCallHit if available, useful to reconstruct a visitor flow across multiple pages.
- bot: true if the hit was made by a bot.
- pageTitle: page title, if provided.
- delaySincePreviousHit: delay in seconds between current hit and its parent.
- pageType: page data type, if provided.

> Note: `parentHit` does not identify users, it only links anonymous visits together. It is designed to remain GDPR-compliant when used properly.

🔁 Listen to PageCallTrackedEvent
--------------------------------

[](#-listen-to-pagecalltrackedevent)

The bundle dispatches an event every time a tracked visit is recorded. You can listen to this event in your app using an EventListener like this example.

```
use ZhorTein\SeoTrackingBundle\Event\PageCallTrackedEvent;

class MyCustomListener
{
    public function __invoke(PageCallTrackedEvent $event): void
    {
        $pageCall = $event->pageCall;
        $hit = $event->pageCallHit;

        // Example: export to your own system
        // or send it to a queue, or just log it
    }
}
```

🔎 Symfony Profiler Integration
------------------------------

[](#-symfony-profiler-integration)

The SEO Tracking Bundle provides a **dedicated panel** in the Symfony Profiler to help developers visualize **UTM parameters** and **routing metadata** for each tracked request.

### What’s displayed in the profiler:

[](#whats-displayed-in-the-profiler)

- The current route name (e.g. `app_homepage`)
- Route parameters (e.g. `{ slug: "example" }`)
- UTM parameters, if present (`utm_campaign`, `utm_source`, `utm_medium`, etc.)

This data helps ensure that campaign tracking is correctly integrated and visible during development.

### ⚠️ Limitations

[](#️-limitations)

The Symfony Profiler only reflects **synchronous request-level data**.

Page tracking hits (`PageCallHit`), which are registered via **asynchronous JavaScript calls** (`fetch()` or `navigator.sendBeacon()`), are **not visible in the profiler toolbar**.

> If you need to debug or analyze `PageCallHit` records, refer to your database directly or use the dedicated interface provided by the future companion tool (under development).

🔁 Customizing Entities via `resolve_target_entities`
----------------------------------------------------

[](#-customizing-entities-via-resolve_target_entities)

By default, the bundle provides two mapped entities: `PageCall` and `PageCallHit`.

However, you may want to extend these entities in your application to store additional information (e.g. link to a `User`, a `Session`, a `Tenant`, etc.).

The bundle supports entity substitution via Symfony’s `resolve_target_entities` mechanism, with zero configuration required.

### ✅ How it works

[](#-how-it-works)

Internally, the bundle defines two interfaces:

- `Zhortein\SeoTrackingBundle\Entity\PageCallInterface`
- `Zhortein\SeoTrackingBundle\Entity\PageCallHitInterface`

These interfaces are resolved to their corresponding classes by default:

```
# config/packages/zhortein_seo_tracking.yaml
zhortein_seo_tracking:
    page_call_class: Zhortein\SeoTrackingBundle\Entity\PageCall
    page_call_hit_class: Zhortein\SeoTrackingBundle\Entity\PageCallHit
```

You can override them by providing your own entity classes:

```
# config/packages/zhortein_seo_tracking.yaml
zhortein_seo_tracking:
    page_call_class: App\Entity\MyCustomPageCall
    page_call_hit_class: App\Entity\MyCustomPageCallHit
```

Your custom classes must implement the interfaces:

```
use Zhortein\SeoTrackingBundle\Entity\PageCallInterface;

#[ORM\Entity]
class MyCustomPageCall implements PageCallInterface
{
    use \Zhortein\SeoTrackingBundle\Entity\Traits\PageCallTrait;

    #[ORM\ManyToOne(targetEntity: User::class)]
    private ?User $user = null;

    // your custom fields here
}
```

You can use the provided `PageCallTrait` and `PageCallHitTrait` to avoid duplicating field declarations or missing fields.

### ⚠️ Notes

[](#️-notes)

- You are not required to declare any resolve\_target\_entities block in your `doctrine.yaml`.
- The bundle takes care of registering the mapping at runtime via the Symfony Dependency Injection system.
- If you do override the entities, don’t forget to generate and apply a new migration:

```
php bin/console make:migration
php bin/console doctrine:migrations:migrate
```

> For technical details on how this is achieved, see the ZhorteinSeoTrackingExtension class and the use of Symfony's prependExtensionConfig() method.

###  Health Score

42

—

FairBetter than 90% of packages

Maintenance84

Actively maintained with recent releases

Popularity8

Limited adoption so far

Community8

Small or concentrated contributor base

Maturity58

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 96.1% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~24 days

Recently: every ~49 days

Total

11

Last Release

84d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/44929672bbed3e80606355419b928ded5440ec6260ecc1013bd543d7bc4d1ec5?d=identicon)[Zhortein](/maintainers/Zhortein)

---

Top Contributors

[![Zhortein](https://avatars.githubusercontent.com/u/82563246?v=4)](https://github.com/Zhortein "Zhortein (74 commits)")[![PEPOUZIN](https://avatars.githubusercontent.com/u/162482502?v=4)](https://github.com/PEPOUZIN "PEPOUZIN (3 commits)")

---

Tags

symfonybundlesymfony-uxtrackingstatsseostatisticsUTMtrack

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Code StylePHP CS Fixer

Type Coverage Yes

### Embed Badge

![Health badge](/badges/zhortein-seo-tracking-bundle/health.svg)

```
[![Health](https://phpackages.com/badges/zhortein-seo-tracking-bundle/health.svg)](https://phpackages.com/packages/zhortein-seo-tracking-bundle)
```

###  Alternatives

[sylius/sylius

E-Commerce platform for PHP, based on Symfony framework.

8.4k5.6M651](/packages/sylius-sylius)[sulu/sulu

Core framework that implements the functionality of the Sulu content management system

1.3k1.3M152](/packages/sulu-sulu)[contao/core-bundle

Contao Open Source CMS

1231.6M2.4k](/packages/contao-core-bundle)[prestashop/prestashop

PrestaShop is an Open Source e-commerce platform, committed to providing the best shopping cart experience for both merchants and customers.

9.0k15.4k](/packages/prestashop-prestashop)[shopware/platform

The Shopware e-commerce core

3.3k1.5M3](/packages/shopware-platform)[netgen/layouts-core

Netgen Layouts enables you to build and manage complex web pages in a simpler way and with less coding. This is the core of Netgen Layouts, its heart and soul.

3689.4k10](/packages/netgen-layouts-core)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
