Recently, I worked on a client project where we had to generate 4 dynamic routes for thousands of individual nodes—without manually creating aliases or redirects. In previous projects, on node edit I would create or edit aliases and redirects on the fly and I wanted to see if we could do it in a more "on the fly" type of way. This led us to leverage inbound and outbound path processors in Drupal, a powerful way to modify URLs on the fly. In this article, I’ll explain how path processors work, share how we used them to handle 10,000+ dynamic routes, and highlight some challenges we faced.
Why We Needed Path Processors
The project required custom URLs for a large dataset of nodes, each needing a unique, readable path. For example:
- Instead of
/node/12345
, we needed URLs like/articles//breaking-news-12345
. - Instead of managing 10,000+ path aliases (which would bloat the database and impact performance), we wanted Drupal to recognize these paths dynamically.
The solution? Path processors—allowing us to transform paths on the fly without storing anything extra.
How Path Processors Work
Drupal path processors intervene at two key moments:
- Inbound Path Processing – When a user visits a URL, this transforms it into a system-understood path (e.g., converting
/articles/breaking-news-12345
to/node/12345
). - Outbound Path Processing – When a URL is generated (e.g., in menus or links), this ensures the system path (
/node/12345
) appears as our custom URL (/articles/breaking-news-12345
).
Our Approach: Dynamic Path Processing
Instead of manually mapping 10,000+ aliases, we built a custom path processor that dynamically recognized URLs based on a pattern. Here’s how:
1. Defining the Service
In your_module.services.yml
, we registered our path processor:
2. Writing the Path Processor
In src/PathProcessor/CustomPathProcessor.php
:
<?php
namespace Drupal\your_module\PathProcessor;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\PathProcessor\InboundPathProcessorInterface;
use Drupal\Core\PathProcessor\OutboundPathProcessorInterface;
use Symfony\Component\HttpFoundation\Request;
/**
* Processes custom article paths dynamically.
*/
class CustomPathProcessor implements InboundPathProcessorInterface, OutboundPathProcessorInterface {
protected $entityTypeManager;
public function __construct(EntityTypeManagerInterface $entity_type_manager) {
$this->entityTypeManager = $entity_type_manager;
}
/**
* Processes inbound paths.
*/
public function processInbound($path, Request $request) {
if (preg_match('/^articles\/([a-z0-9\-]+)-(\d+)$/', $path, $matches)) {
return '/node/' . $matches[2]; // Extract node ID
}
return $path;
}
/**
* Processes outbound paths.
*/
public function processOutbound($path, &$options) {
if (preg_match('/^\/node\/(\d+)$/', $path, $matches)) {
$node = $this->entityTypeManager->getStorage('node')->load($matches[1]);
if ($node && $node->bundle() == 'article') {
$title = strtolower(str_replace(' ', '-', $node->getTitle()));
return "/articles/{$title}-{$matches[1]}";
}
}
return $path;
}
}
How This Works
- Inbound: If a user visits
/articles/breaking-news-12345
, it extracts the node ID (12345
) and resolves it as/node/12345
. - Outbound: When Drupal generates a link to
/node/12345
, it converts it into/articles/title-based-on-node-12345
.
Challenges & Lessons Learned
1. Complex Path Matching
Since paths varied, we used regular expressions (preg_match()
) to correctly extract node IDs. This took some trial and error, especially handling edge cases like special characters in titles.
2. Debugging Was Tricky
Unlike traditional routing, path processors work invisibly. To debug, we had plenty of logging.
3. Caching & Performance
Drupal caches routes aggressively, so after changes, as with anything Drupal, we had to clear the cache
Final Thoughts
Path processors saved us from managing thousands of aliases while keeping URLs clean and user-friendly. If you’re dealing with dynamic URL structures, consider using path processors instead of relying solely on aliases or redirects.