---
title: "Important: #101699 - Messenger wildcard routes act as a fallback"
manual: "TYPO3 Core Changelog"
version: "main"
permalink: "https://docs.typo3.org/permalink/changelog:important-101699-1784329899"
source: "Changelog/14.3.x/Important-101699-MessengerWildcardRoutesActAsFallback.rst"
modified: "2026-09-15T10:46:36+00:00"
---

# Important: #101699 - Messenger wildcard routes act as a fallback

See [forge#101699](https://forge.typo3.org/issues/101699)

## Description

The Symfony Messenger transport routing configured via
`$GLOBALS['TYPO3_CONF_VARS']['SYS']['messenger']['routing']` maps a message
*type* to one or more transport identifiers.

So far a wildcard route - a namespace wildcard (`My\Message\*`) or the
global wildcard `'*'` \- was *always* applied *in addition* to any matching
specific route. A message that matched both a specific route and a wildcard
route was therefore dispatched to both transports. Because TYPO3 ships a default
catch-all route `'*' => 'default'`, every routed message was silently sent
to the `default` transport as well, and routing a message to its specific
transport *only* required removing the wildcard route explicitly, typically in
an extension's `ext_localconf.php`:

```php
// previously required to avoid the additional wildcard routing
unset($GLOBALS['TYPO3_CONF_VARS']['SYS']['messenger']['routing']['*']);
```

A wildcard route now acts as a **fallback**: it is skipped as soon as a more
specific type already matched, and only applies when no specific route matched.
This mirrors the upstream Symfony `SendersLocator` resolution, so the
shipped default `'*' => 'default'` route now behaves as a true fallback and
the manual `unset()` of the wildcard route is no longer needed:

```php
$GLOBALS['TYPO3_CONF_VARS']['SYS']['messenger']['routing'] = [
    // handled by the "doctrine" transport only, the '*' route is skipped
    \My\Extension\Message\ImportMessage::class => 'doctrine',
    // fallback for every message without a more specific route
    '*' => 'default',
];
```

## Single and multiple transports per route

A routing value may be given as a single transport identifier (a string) or as
an ordered list of transport identifiers (an array) to route a single message to
several transports on purpose:

```php
$GLOBALS['TYPO3_CONF_VARS']['SYS']['messenger']['routing'] = [
    // single transport
    \My\Extension\Message\ImportMessage::class => 'doctrine',
    // sent to "audit" and "doctrine", in that order
    \My\Extension\Message\AuditableInterface::class => ['audit', 'doctrine'],
    '*' => 'default',
];
```

The legacy mapping syntax using a plain string
(`\My\Message::class => 'doctrine'`) keeps working unchanged and is treated
as the single-element array form (`\My\Message::class => ['doctrine']`).

## Wildcards are supported on every namespace level

Wildcard routing is not limited to the global `'*'` route. A namespace
wildcard may be configured on *any* level and is likewise treated as a fallback
that is only used when no more specific route matched:

```php
$GLOBALS['TYPO3_CONF_VARS']['SYS']['messenger']['routing'] = [
    // every message below \My\Extension\Message\Import\
    \My\Extension\Message\Import\* => 'import',
    // every other message below \My\Extension\Message\
    \My\Extension\Message\* => 'doctrine',
    // global fallback
    '*' => 'default',
];
```

## How a message is matched against the routing configuration

The routing configuration is a plain lookup map; its key *order* is irrelevant.
The order in which routes are considered comes from the **type chain of the
dispatched message**, derived from the message's PHP type (via
`\Symfony\Component\Messenger\Handler\HandlersLocator::listTypes()`), not
from the order of the entries in the global configuration array.

For a dispatched message the type chain is evaluated from most to least
specific:

1.  the concrete message class,
1.  its parent classes (bottom-up),
1.  its implemented interfaces,
1.  the namespace wildcards for every namespace level (for example
    `My\Extension\Message\Import\*`, `My\Extension\Message\*`,
    `My\Extension\*`, `My\*`),
1.  and finally the global wildcard `'*'`.

Senders are collected across every matching type in that order and deduplicated
(first occurrence wins). The first non-wildcard match therefore "locks in" the
transports and every following wildcard route is skipped. This resolution
depends solely on the class, parent and interface hierarchy of the message and
is entirely independent of the shape or ordering of the
`['SYS']['messenger']['routing']` array - the configuration only provides
the mapping from a type identifier to its transports.

## Impact

Installations that (knowingly or not) relied on a message being routed to both
its specific transport *and* the wildcard transport - most notably the default
`'*' => 'default'` route - will see such a message routed to its specific
transport only. Configure the additional transport explicitly as a list value
(`\My\Message::class => ['specific', 'default']`) where the previous
fan-out is still wanted.

## Providing this change on older TYPO3 versions

The change is contained in the core
`\TYPO3\CMS\Core\Messenger\TransportLocator`. Projects that need the
fallback resolution on an older TYPO3 version that does not ship it yet (for
example TYPO3 v12) can obtain it in one of two ways.

### Replace the core implementation via Symfony dependency injection

Copy the current `TransportLocator` into an extension, for example as
`\MyVendor\MyExtension\Messenger\FallbackTransportLocator`, keeping the
`#[AutowireLocator('messenger.sender', indexAttribute: 'identifier')]`
constructor argument. Then re-point the messenger senders-locator to it in the
extension's `Configuration/Services.yaml`:

```yaml
services:
  MyVendor\MyExtension\Messenger\FallbackTransportLocator:
    arguments:
      $sendersLocator: !tagged_locator { tag: 'messenger.sender', index_by: 'identifier' }

  # replace the core implementation used for message routing
  TYPO3\CMS\Core\Messenger\TransportLocator:
    alias: MyVendor\MyExtension\Messenger\FallbackTransportLocator

  Symfony\Component\Messenger\Transport\Sender\SendersLocatorInterface:
    alias: MyVendor\MyExtension\Messenger\FallbackTransportLocator
```

Because the messenger senders-locator is resolved through this alias, the
extension implementation is used instead of the core one and the fallback
resolution becomes available on the older core version. If the
`WebhookTypesProvider` display of the resolved transports is required as
well, copy its adjusted version accordingly.

### Backport the change via a Composer patch

Alternatively, apply the change to `typo3/cms-core` directly using a
Composer patch tool such as [cweagans/composer-patches](https://github.com/cweagans/composer-patches):

```json
{
    "require": {
        "cweagans/composer-patches": "^1.7"
    },
    "extra": {
        "patches": {
            "typo3/cms-core": {
                "Messenger wildcard routing fallback": "patches/messenger-wildcard-fallback.patch"
            }
        }
    }
}
```

The patch file *should* contain the diff of
`typo3/sysext/core/Classes/Messenger/TransportLocator.php` (and, if used,
the `WebhookTypesProvider.php` adjustment as a [`typo3/cms-webhooks`](https://packagist.org/packages/typo3/cms-webhooks)
patch file) and is applied automatically on `composer install`.

> [!NOTE]
> Due to the monorepo to composer package splitting this requires adoption of
> the paths and stripping out changes in `Tests/` folders, and for each
> affected TYPO3 system extension a dedicated patch file needs to be created
> and provided.
>
> See [Applying Core patches](https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/Administration/Upgrade/ApplyingCorePatches/Index.html#applying-core-patches)
> for further details on how to create and apply Composer patches for TYPO3.
