Closed (fixed)
Project:
Domain
Version:
3.x-dev
Component:
Code
Priority:
Normal
Category:
Feature request
Assigned:
Unassigned
Reporter:
Created:
20 Feb 2026 at 13:31 UTC
Updated:
9 Mar 2026 at 15:15 UTC
Jump to comment: Most recent
The Domain Source module has a path processor that allows \Drupal\Core\Url objects to specify the target domain. This is a very useful feature, but is currently aimed to support a specific use case: force the outbound path to the Domain Source.
Allow Url objects to specify the Domain as an option:
None
Url objects can now specify the Domain as an option
None
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
Comments
Comment #2
mably commentedHi @idebr, can you give a few use cases (besides source domain) for this feature?
Comment #3
idebr commentedA project I am working on uses the domain option for Url objects for the following purposes:
Content is assigned to multiple domains, but the canonical URL is set to the Domain source URL to prevent duplicate content in search engines:
Search results include results from other domains, but link to the Domain Source if not available on the current domain:
Clickable cards have an additional overlay link:
Comment #4
mably commentedIsn't
domain_sourcesupposed to do that automatically? 🤔Comment #5
idebr commentedRewriting the canonical is disabled for this project. Reading content from different Domain sources would result in switching domains, so visitors would lose their orientation.
Comment #6
mably commentedAnalysis
The
DomainSourcePathProcessoralready supports an explicitdomain_target_idURL option that bypasses the entity field lookup. So you can already do:$url->setOption('domain_target_id', 'example_com');But this only works when
domain_sourceis installed, since the path processor lives entirely in that submodule. The basedomainmodule has no outbound path processor at all.The core rewriting logic in
DomainSourcePathProcessoris just two lines:$options['base_url'] = $source_domain->getPath();$options['absolute'] = TRUE;The proposal would split the 664-line processor into two concerns:
domainmodule — a lightweight outbound path processor that reads adomainoption from the URL and rewritesbase_url+absolute. No entity awareness, no route matching — just "if a domain is specified, use it."domain_sourcemodule — keeps all the entity/route/field logic, but instead of rewriting the URL itself, it sets$options['domain']and lets the base processor handle the rewrite.This would allow any custom code to target a specific domain on a
Urlobject without needingdomain_sourceenabled — covering the use cases described (canonical URLs, cross-domain search links, card overlays).Considerations
domain_sourceprocessor also handles language negotiation re-processing, destination parameter rewriting, and alter hooks (hook_domain_source,hook_domain_source_path). Those would stay indomain_source.domain_sourcesets the option but before the final URL is built.domain_target_idoption could be kept for BC, with the newdomainoption accepting either a domain entity or a machine name.Comment #8
mably commentedSummary
Added a lightweight outbound path processor to the base domain module, enabling any code to target a specific domain on a
Urlobject without installingdomain_source:Commit 1:
feat: #3574800 Allow Url objects to specify the Domain as an optionNew file
domain/src/HttpKernel/DomainPathProcessor.phpimplementsOutboundPathProcessorInterface:domainURL optionDomainInterfaceentity or machine name string$options['base_url']and$options['absolute'] = TRUEwhen validEntityTypeManagerInterface(autowired)New kernel test file
domain/tests/src/Kernel/DomainPathProcessorTest.phpwith 7 test methods covering:DomainInterfaceentitydomainoption is absentModified
domain/domain.services.ymlregisters the service asdomain.path_processorwithpath_processor_outboundtag at priority 80 — runs afterdomain_source(90).DomainSourcePathProcessorwas not changed in this commit.Comment #9
mably commentedCommit 2: Delegate URL rewriting from DomainSourcePathProcessor to DomainPathProcessor
DomainSourcePathProcessor(priority 90) no longer rewritesbase_url/absoluteitself. Instead, when a source domain differs from the active domain, it sets$options['domain'] = $sourceand letsDomainPathProcessor(priority 80) handle the actual URL rewriting.Changes in
DomainSourcePathProcessor::processOutbound():$options['domain']is already set — avoids double-processing.$options['base_url'] = rtrim($source->getPath(), '/')+$options['absolute'] = TRUE+addCacheContexts(['url.site'])with$options['domain'] = $source.Commit 3: Move cross-domain URL rewriting features to DomainPathProcessor
Language negotiation re-processing and destination parameter handling are not domain_source-specific — they apply to any cross-domain URL rewrite. Moving them into
DomainPathProcessorensures any code setting$options['domain']automatically gets these features.DomainPathProcessor (domain module)
ConfigFactoryInterface,DomainNegotiationContext,?LanguageNegotiatorInterfaceisLanguageNegotiationEnabled(),isSameLanguageNegotiationConfig(),processLanguageNegotiationOutbound(),isDestinationDomainEnabled(),processDestinationParameter(),reset()processOutbound()now runs cross-domain features (language negotiation + destination parameter) after resolving the domain and before settingbase_url/absoluteaddCacheableDependency($domain)is added unconditionally (cache tag invalidation when the domain entity changes);url.sitecache context is added only when destination parameter processing is active (the only output that includes the current domain's host)DomainSourcePathProcessor (domain_source module)
$languageNegotiatorconstructor parameter andLanguageNegotiatorInterfaceimportreset()— no longer clears language-related cachesSettings migration
domain_source.settingstodomain.settings:source_language_negotiation→language_negotiationallow_destination_domain→allow_destination_domainDomainSourceSettingsFormtoDomainSettingsForm, wrapped in a closed-by-default "Experimental features" details groupUpdate hooks
domain_update_10002()— Initializeslanguage_negotiationandallow_destination_domainindomain.settings(defaults toFALSE)domain_source_update_10004()— Migrates values fromdomain_source.settingstodomain.settingsand clears the old keysTests
DomainSourceLanguageNegotiationTest: config references updated todomain.settings:language_negotiation;clearCaches()now also resetsdomain.path_processorDomainSourceDestinationKernelTest: config and service references updated todomain.settings/domain.path_processorComment #10
mably commentedDocumentation and DomainSourceToken Simplification
Two new documentation commits added:
1. Documentation pages (
docs/)New page
docs/domain/index.mddocuments the Domain module's cross-domain URL rewriting:$options['domain']URL option usage with machine name string orDomainInterfaceentityDomainPathProcessor::processOutbound()resolves the domain, applies features, rewrites URLs, and adds cache metadataDomainSourcePathProcessorsets$options['domain']and delegatesLanguageNegotiationUrlin the target domain's context when configs differdestination_domainquery parameter for cross-domain redirect accuracyUpdated page
docs/domain_source/index.md:$options['domain'][node:canonical-source-domain-url]token — useful when canonical route is added to Excluded entity route suffixesmkdocs.yml— Added the "Domain" nav entry.2. Simplify
DomainSourceTokenRefactored to use
$options['domain']mechanism instead ofgetExcludedRoutes()logic.Before: The token checked if canonical was in excluded routes. If excluded, it manually built the URL via
Domain::buildUrl(). Otherwise, it calledtoUrl()->setAbsolute()->toString()and relied on the path processor.After: The token sets
$url->setOption('domain', $source)on the Url object and letsDomainPathProcessorhandle rewriting. This works regardless ofexclude_routesconfiguration — whenDomainSourcePathProcessorencounters a URL with$options['domain']already set, it returns immediately.Uses
$url->setAbsolute()->toString(TRUE)to get aGeneratedUrlobject (which extendsBubbleableMetadata) instead of a plain string. This captures all cache metadata from the URL generation chain — including the domain entity's cache tags added byDomainPathProcessor. The URL string is extracted viagetGeneratedUrl()and all cache metadata is merged into the token's$bubbleable_metadataviamerge().Removed:
ConfigFactoryInterfacedependency$excludedRoutespropertygetExcludedRoutes()methodThe
DomainSourceTokenTestpasses (1 test, 8 assertions).Comment #11
mably commentedBC Breaking Changes Analysis
Analysis of the full changeset from #3574800 (5 commits) evaluated as a single before/after comparison.
Medium
1. Public API removed from
DomainSourceToken$excludedRoutes— removedgetExcludedRoutes()— removed__construct(ConfigFactoryInterface)— removed entirelyImpact: Any code accessing the property, calling the method, or instantiating/subclassing
DomainSourceTokenwill break.Low
2. Constructor changes in
DomainSourcePathProcessor?LanguageNegotiatorInterface $languageNegotiator(last parameter) — removed$pathMatcherand$sourceHelper— swapped ($sourceHelperis now before$pathMatcher, positioning$pathMatcherlast for easier future removal)Impact: Only affects subclasses calling
parent::__construct()with positional arguments; normal DI autowiring is unaffected.Summary
DomainSourceTokenDomainSourcePathProcessorComment #12
idebr commentedWow, this is looking great! Thanks for taking up this issue.
A minor remark which I'll leave to you to decide: I would suggest the Url domain option is required to be Domain object similar to language: https://git.drupalcode.org/project/drupal/-/blob/main/core/lib/Drupal/Co...
The Domain entity is typically autoloaded when working with entity reference fields anyway, and making the option take two different data types complicates code analysis.
Also I noticed there is currently no way to get domain path alias processing to apply to the URL object, since this is locked behind the domain_source logic. This is better fixed in the domain_path module by overwriting the \Drupal\path_alias\PathProcessor\AliasPathProcessor. I'll open an issue in the domain_path issue queue as a follow up.
Comment #14
mably commentedComment #16
idebr commented🙏
Related issue in Domain path for applying the path alias: #3575163: Apply domain path alias processing on outbound URLs with a domain option