I have a page my/topics. The page presents content based on the user's selection from another page. Having made the selection, it and other information is passed to the page via a query string:


I have no control over the creation of the incoming path. For the processing of the path and for SEO I need the path to instead be:


In both cases the target is my/topics, but the processing I need from that point depends on being able to access the additional information as part of the URL instead of as a query string.

The available topics are a large non-static list. It might be possible to do some regex magic in .htaccess to handle it as a redirect, but I'd rather handle it within Drupal, if possible.

To that end I've used OutboundPathProcessorInterface. The class is listed, below. The problem I am having is that the path is not changed in the browser address bar. If I put my/topics in a menu link on the page, or a link in the body text, it does become my/topics/topic1 in each of the links, and in the address bar on a subsequent click, but not in the address bar of the page that is rendered as part of the altering of the path... it appears as my/topics.

I have commented out the 'if' statement that limits the change to just that path, and the result is the same on admin/structure, for example. The browser shows admin/structure, but the link to it in the admin menu when hovered over shows admin/structure/topic1.

You'll note that I have the priority of the module being -32766 (I've also tried just -1). No difference.


    class: Drupal\url_remap\UrlRemapOutboundPathProcessor
      - { name: path_processor_outbound, priority: -32766 }


namespace Drupal\url_remap;

use Drupal\Core\PathProcessor\OutboundPathProcessorInterface;
use Drupal\Core\Render\BubbleableMetadata;
use Symfony\Component\HttpFoundation\Request;

class UrlRemapOutboundPathProcessor implements OutboundPathProcessorInterface {
  function processOutbound($path, &$options = array(), Request $request = NULL, BubbleableMetadata $bubbleable_metadata = NULL) {
    $append = '';
    if ($path == "/my/topics") {
      if (sizeof($options['query']) > 0) {
        if (array_key_exists('choice', $options['query'])) {
          $append = substr($path, strlen($path) - 1, 1) != '/' ? '/' . $options['query']['choice'] : $options['query']['choice'];
    return $path . $append;

1 Answer 1


You need three components for this task:

Your outbound path processor UrlRemapOutboundPathProcessor::processOutbound is the first step to get this started.

Second add an inbound path processor UrlRemapOutboundPathProcessor::processInbound to the class similar to Drupal\system\PathProcessor\PathProcessorFiles\processInbound:

  public function processInbound($path, Request $request) {
    if (strpos($path, '/system/files/') === 0 && !$request->query->has('file')) {
      $file_path = preg_replace('|^\/system\/files\/|', '', $path);
      $request->query->set('file', $file_path);
      return '/system/files';
    return $path;

Your path processor won't change the URL in the browser, this is by the way the same with other path processors, for example you can access nodes still by their internal paths (/node/[id]) after you've entered an alias.

So third you need a request event subscriber which redirects the unwanted internal URLs.

An example would be how Redirect does this in RouteNormalizerRequestSubscriber::onKernelRequestRedirect:

   * Performs a redirect if the URL changes in routing.
   * The redirect happens if a URL constructed from the current route is
   * different from the requested one. Examples:
   * - Language negotiation system detected a language to use, and that language
   *   has a path prefix: perform a redirect to the language prefixed URL.
   * - A route that's set as the front page is requested: redirect to the front
   *   page.
   * - Requested path has an alias: redirect to alias.
   * @param \Symfony\Component\HttpKernel\Event\GetResponseEvent $event
   *   The Event to process.
  public function onKernelRequestRedirect(GetResponseEvent $event) {


Probably this global redirect catches the internal paths from step one/two as well. If you don't want to redirect all URLs globally or find issues with the alpha version of this module, you can use this event subscriber as example to write custom code to target only the specific URLs.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.