Dynamic routing is a crucial aspect of Drupal's flexible and extensible architecture. It enables developers to define their own routes dynamically, which can be highly beneficial in certain situations.

Recently, I had to generate dynamic routes for several custom plugins and was able to implement this using Drupal's dynamic routing concepts.

Context

I was developing a wrapper module with multiple utility plugins, so I defined a plugin type within the module to allow for dynamic creation of these plugins. Later, a requirement arose for unique routes for each plugin, to present the plugin form to users. To accomplish this, I utilized Drupal's dynamic routing feature.

Dynamic Routes With Custom Plugin Type

Implementation

1. Add 'route_callbacks' in modules routing.yml

route_callbacks:
  - '\Drupal\webutilities\Routing\RoutesProvider::routes'

2. Create a routing class

Here route {plugin} parameter is casted to webutility (Custom plugin type) plugin.

<?php

namespace Drupal\webutilities\Routing;

use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\webutilities\WebUtilityPluginManager;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;

/**
 * Provides dynamic routes of plugins.
 */
class RoutesProvider implements ContainerInjectionInterface {

  /**
   * Plugin manager service.
   *
   * @var \Drupal\webutilities\WebUtilityPluginManager
   */
  protected $pluginManager;

  /**
   * RoutesProvider constructor.
   */
  public function __construct(WebUtilityPluginManager $webUtilityPluginManager) {
    $this->pluginManager = $webUtilityPluginManager;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    return new static(
      $container->get('plugin.manager.webutility')
    );
  }

  /**
   * {@inheritdoc}
   */
  public function routes() {
    $route_collection = new RouteCollection();

    $plugins = $this->pluginManager->getDefinitions();
    foreach ($plugins as $key => $plugin) {
      $route_id = "webutilities.{$key}";
      $route = new Route(
      // Path to attach this route to:
        'module/{plugin}',
        // Route defaults:
        [
          '_controller' => '\Drupal\webutilities\Controller\ModuleDisplayController::moduleDisplay',
          '_title_callback' => '\Drupal\webutilities\Controller\ModuleDisplayController::getPluginTitle',
          'type' => 'webutility',
        ],
        // Route requirements:
        [
          '_permission' => 'access utilities modules',
        ],
        [
          'parameters' => [
            'plugin' => ['type' => 'webutility'],
          ],
        ]
      );
      $route_collection->add($route_id, $route);
    }
    return $route_collection;
  }

}

3. Add display controller

<?php

namespace Drupal\webutilities\Controller;

use Drupal\Component\Utility\Html;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Extension\ModuleExtensionList;
use Drupal\Core\Form\FormBuilderInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\Url;
use Drupal\webutilities\Utility\Utility;
use Drupal\webutilities\WebUtilityEntityInterface;
use Drupal\webutilities\WebUtilityInterface;
use Drupal\webutilities\WebUtilityPluginManager;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;

/**
 * Handles utilities module list.
 */
class ModuleDisplayController extends ControllerBase implements ContainerInjectionInterface {

  use StringTranslationTrait;

  /**
   * Plugin manager service.
   *
   * @var \Drupal\webutilities\WebUtilityPluginManager
   */
  protected $pluginManager;

  /**
   * Module extension list service.
   *
   * @var \Drupal\Core\Extension\ModuleExtensionList
   */
  protected $moduleExtensionList;

  /**
   * ModuleDisplayController constructor.
   */
  public function __construct(WebUtilityPluginManager $webUtilityPluginManager,
                              ModuleExtensionList     $moduleExtensionList,
                              FormBuilderInterface    $formBuilder) {
    $this->pluginManager = $webUtilityPluginManager;
    $this->moduleExtensionList = $moduleExtensionList;
    $this->formBuilder = $formBuilder;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    return new static(
      $container->get('plugin.manager.webutility'),
      $container->get('extension.list.module'),
      $container->get('form_builder')
    );
  }

  /**
   * Render plugin form.
   *
   * @param \Drupal\webutilities\WebUtilityInterface $plugin
   *   Plugin object.
   * @param \Symfony\Component\HttpFoundation\Request $request
   *   Current request.
   *
   * @return array
   *   Render-able array.
   */
  public function moduleDisplay(WebUtilityInterface $plugin, Request $request) {
    $body = NULL;
    $config = Utility::loadPluginConfig($plugin->getPluginId());
    if ($config instanceof WebUtilityEntityInterface) {
      $body = $config->getContent();
      if (isset($body['value'])) {
        $body = [
          '#type' => 'processed_text',
          '#text' => @$body['value'],
          '#format' => 'full_html',
        ];
      }
    }

    $form = $this->formBuilder->getForm('\Drupal\webutilities\Form\WebUtilityDynamicForm', $plugin);

    $build = [
      '#theme' => 'module_form',
      '#form' => $form,
      '#body' => $body,
    ];
    return $build;
  }

  /**
   * Return Plugin Title.
   *
   * @param \Drupal\webutilities\WebUtilityInterface $plugin
   *   Plugin object.
   * @param \Symfony\Component\HttpFoundation\Request $request
   *   Current request.
   */
  public function getPluginTitle(WebUtilityInterface $plugin, Request $request) {
    $defination = $plugin->getPluginDefinition();
    $module_path = $this->moduleExtensionList->getPath($defination['provider']);

    return [
      '#type' => 'inline_template',
      '#template' => '<img width="50" src="{{ logo }}" class="module-img" alt="{{ title }}" /> {{ title }}',
      '#context' => [
        'logo' => "/{$module_path}/assets/images/{$plugin->getPluginId()}.svg",
        'title' => $plugin->label(),
      ],
    ];
  }

}

There are different ways to implement dynamic routing in Drupal. One way is by using the method described above. Another option is to develop a Route Subscriber (event_subscriber). However, the method described above has worked well for me.