Skip to main content
Version: 2026.2

Visitor Info

The VisitorInfo is the central data object passed to every part of the targeting system. The engine creates a new VisitorInfo for every request and enriches it with data throughout the matching process. Starting from an empty object, several components add data to it, so it ends up holding all relevant targeting data for the current request.

Visitor ID

To identify returning visitors, the engine needs a unique identifier assigned to each visitor. This identifier, internally called the visitor ID, is generated or set by the browser and stored in a cookie. When creating a VisitorInfo, the targeting engine tries to load a visitor ID from the _pc_vis cookie.

By design, the frontend targeting.js script generates this ID: when a site with targeting enabled loads and no visitor ID was previously set, it generates a random string and exposes a method to set it manually:

_ptg.api.setVisitorId('my-custom-visitor-id');

Upon setting a visitor ID, the script stores it in two places in the browser:

  • a _pc_vis cookie
  • a _ptg.user local storage entry, holding persistent data about the visitor (an activity log and a list of previous visitor IDs). If the visitor ID cookie is missing but the local storage entry has an ID, that ID is used to write a new cookie.

The visitor ID is used wherever a unique identifier is needed, for example to recognize returning visitors or to persist data for a specific visitor in a targeting storage.

Accessing the Visitor Info in Your Code

Similar to the security token in Symfony's security system, fetch the current VisitorInfo from the VisitorInfoStorage. This storage is defined as a service: inject it into your own services, or use it as a controller action argument when your controllers are registered as services. Declare VisitorInfoStorageInterface as a dependency and Symfony's autowiring takes care of the rest.

As an example, a sample service working with the VisitorInfo:

<?php

namespace App\Targeting;

use Pimcore\Bundle\PersonalizationBundle\Targeting\VisitorInfoStorageInterface;

class MyService
{
private VisitorInfoStorageInterface $visitorInfoStorage;

public function __construct(VisitorInfoStorageInterface $visitorInfoStorage)
{
$this->visitorInfoStorage = $visitorInfoStorage;
}

public function getVisitorId(): ?string
{
// always check if there is a visitor info before trying to fetch it
if (!$this->visitorInfoStorage->hasVisitorInfo()) {
return null;
}

$visitorInfo = $this->visitorInfoStorage->getVisitorInfo();

return $visitorInfo->getVisitorId();
}
}

With autowiring enabled, defining the service is enough; the type hint against VisitorInfoStorageInterface lets Symfony autowire it:

services:
_defaults:
autowire: true
autoconfigure: true
public: false

App\Targeting\MyService: ~

Without autowiring, wire the dependency manually instead:

services:
App\Targeting\MyService:
arguments:
$visitorInfoStorage: '@Pimcore\Bundle\PersonalizationBundle\Targeting\VisitorInfoStorageInterface'

If your controllers are defined as services, use argument injection:

<?php

namespace App\Controller;

use Pimcore\Bundle\PersonalizationBundle\Targeting\VisitorInfoStorageInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Routing\Attribute\Route;

class VisitorInfoController
{
#[Route('/visitor-info')]
public function visitorInfoAction(VisitorInfoStorageInterface $visitorInfoStorage): JsonResponse
{
$data = [
'visitorId' => null
];

// always check if there is a visitor info before trying to fetch it
if ($visitorInfoStorage->hasVisitorInfo()) {
$visitorInfo = $visitorInfoStorage->getVisitorInfo();

$data['visitorId'] = $visitorInfo->getVisitorId();
}

return new JsonResponse($data);
}
}