Skip to main content
Version: 2026.2

Conditions

Conditions are logical blocks that combine with other conditions inside a targeting rule. A condition implements a match() method that always returns a boolean.

Shipped Conditions

The bundle registers the following conditions by default (config/pimcore/default.yaml); the type key is what appears in rule configuration and in the pimcore_personalization.targeting.conditions map:

Type keyClass
browserPimcore\Bundle\PersonalizationBundle\Targeting\Condition\Browser
countryPimcore\Bundle\PersonalizationBundle\Targeting\Condition\Country
geopointPimcore\Bundle\PersonalizationBundle\Targeting\Condition\GeoPoint
hardwareplatformPimcore\Bundle\PersonalizationBundle\Targeting\Condition\HardwarePlatform
languagePimcore\Bundle\PersonalizationBundle\Targeting\Condition\Language
operatingsystemPimcore\Bundle\PersonalizationBundle\Targeting\Condition\OperatingSystem
referringsitePimcore\Bundle\PersonalizationBundle\Targeting\Condition\ReferringSite
searchenginePimcore\Bundle\PersonalizationBundle\Targeting\Condition\SearchEngine
target_groupPimcore\Bundle\PersonalizationBundle\Targeting\Condition\TargetGroup
timeonsitePimcore\Bundle\PersonalizationBundle\Targeting\Condition\TimeOnSite
urlPimcore\Bundle\PersonalizationBundle\Targeting\Condition\Url
visitedpagesbeforePimcore\Bundle\PersonalizationBundle\Targeting\Condition\VisitedPagesBefore

To add a custom condition, implement two parts:

Implementing a Condition

The most important method on ConditionInterface is match(), which receives the current VisitorInfo instance and returns a boolean indicating whether the condition matches.

As an example, build a condition that matches once the current time of day is later than a configured hour. If configured to 15, the condition starts matching at 15:00 and keeps matching until midnight.

Start with a condition class implementing ConditionInterface. For simplicity, this only checks the current hour, not the full time:

<?php

// src/Targeting/Condition/TimeOfTheDay.php

namespace App\Targeting\Condition;

use Pimcore\Bundle\PersonalizationBundle\Targeting\Condition\ConditionInterface;
use Pimcore\Bundle\PersonalizationBundle\Targeting\Model\VisitorInfo;

class TimeOfTheDay implements ConditionInterface
{
private ?int $hour;

public function __construct(?int $hour = null)
{
$this->hour = $hour;
}

public static function fromConfig(array $config): self
{
$hour = $config['hour'] ?? null;
if (!empty($hour)) {
$hour = (int)$hour;
}

// build an instance from the config as configured
// in the targeting rule
return new self($hour);
}

public function canMatch(): bool
{
// basic validation if the condition is able to match
return null !== $this->hour && $this->hour >= 0 && $this->hour <= 23;
}

public function match(VisitorInfo $visitorInfo): bool
{
$hour = (int)(new \DateTime())->format('H');

return $hour >= $this->hour;
}
}

After implementing your condition, register it with the following configuration. The identifier timeoftheday is later reused by the Studio dynamic type, so choose a unique name and keep it consistent between the PHP class and the frontend registration.

pimcore_personalization:
targeting:
conditions:
timeoftheday: App\Targeting\Condition\TimeOfTheDay

Building a Condition Instance

When building an instance of your condition, by default the ConditionFactory calls the static fromConfig() method with the data configured on the targeting rule. Avoid injecting services or custom data into your condition; use the data provider system instead to add data to the VisitorInfo. If you need more control over how your condition is built, either:

  • Override the ConditionFactory service definition (not recommended) and implement your own logic instead of calling fromConfig().
  • Handle the TargetingEvents::BUILD_CONDITION event and set an instance of your condition on the event. The BuildConditionEvent carries everything needed to build a condition instance (type, class name, config data). If you set a condition on the event via setCondition(), the standard logic is skipped and your condition is used instead. See Events for the full event reference.

Condition Data

If your condition needs outside data, implement DataProviderDependentInterface and list the data provider keys that need to be set on the VisitorInfo before matching. The Data Providers chapter enhances this TimeOfTheDay condition with one. For further examples, see the shipped conditions.

If your condition needs to run logic before or after matching (for example to trigger a side effect through the event dispatcher), implement EventDispatchingConditionInterface and its preMatch() / postMatch() methods; the ConditionMatcher calls them around every match.

Condition Variables

Another important part is variable conditions, which support the session_with_variables rule matching scope. A condition implementing this interface returns an array of the variables that led to the match from getMatchedVariables(). Pimcore uses this data to determine whether the rule already ran with the exact same data.

Implement this interface whenever possible. Use AbstractVariableCondition as a starting point; it contains helper methods to collect variable data. Build your data deterministically (for example, keep the same key order in an array, or the same serialization format), since Pimcore hashes this data to compare it against previous evaluations.

For example, the country condition sets the ISO country code that led to the match as its data (based on GeoLocation). If a rule runs in the session_with_variables scope and the country condition is the only condition on that rule, it won't run twice for the same resolved country.

The TimeOfTheDay condition can easily be enhanced to store variables. The variable to store is the resolved current hour.

<?php

namespace App\Targeting\Condition;

use Pimcore\Bundle\PersonalizationBundle\Targeting\Condition\AbstractVariableCondition;
use Pimcore\Bundle\PersonalizationBundle\Targeting\Model\VisitorInfo;

class TimeOfTheDay extends AbstractVariableCondition
{
// ...

public function match(VisitorInfo $visitorInfo): bool
{
$hour = (int)(new \DateTime())->format('H');

if ($hour >= $this->hour) {
$this->setMatchedVariable('hour', $hour);

return true;
}

return false;
}
}

Registering in Pimcore Studio

Registering the PHP condition makes it usable in targeting rules, but it won't appear in the Pimcore Studio rule editor until you also register a matching Studio dynamic type.

The rule builder has no global condition registry. Its RuleConditions component takes a registry instance as a prop, so each consuming bundle owns its own. This bundle creates an instance of the SDK class DynamicTypeRuleConditionRegistry and binds it in the Studio DI container under the service ID PersonalizationBundle/ConditionRegistry. The Conditions tab renders only the types held by that instance, so your dynamic type has to be registered on it. Registering against any other instance leaves it invisible here.

To add a Studio dynamic type for timeoftheday, build a small Studio UI plugin bundle that:

  • implements a dynamic type extending DynamicTypeRuleConditionAbstract from @pimcore/studio-ui-bundle/modules/rule-builder, decorated with @injectable(), using timeoftheday as its type key so it matches the PHP registration,
  • binds that class in the container in the plugin's onInit,
  • resolves PersonalizationBundle/ConditionRegistry and calls registerDynamicType() on it in the plugin's onStartup.
import { container } from '@pimcore/studio-ui-bundle/app'
import { type DynamicTypeRuleConditionRegistry } from '@pimcore/studio-ui-bundle/modules/rule-builder'

export const MyPlugin: IAbstractPlugin = {
name: 'my-targeting-plugin',
onInit: ({ container }) => {
container.bind('MyBundle/DynamicTypes/Condition/TimeOfTheDay')
.to(DynamicTypeTargetingConditionTimeOfTheDay).inSingletonScope()
},
onStartup: () => {
const registry = container.get<DynamicTypeRuleConditionRegistry>('PersonalizationBundle/ConditionRegistry')
registry.registerDynamicType(container.get('MyBundle/DynamicTypes/Condition/TimeOfTheDay'))
}
}
caution

Resolve the registry in onStartup, not in your plugin's onInit. The plugin system runs every plugin's onInit before any onStartup, so this bundle's binding may not exist yet while your onInit is running.

See this bundle's own condition dynamic types for concrete examples (for instance the URL condition), and the Studio UI Bundle's Dynamic Types and Getting Started with Your First Plugin guides for the general plugin and dynamic type mechanism.