7.x system.api.php hook_url_outbound_alter(&$path, &$options, $original_path)

Alters outbound URLs.


$path: The outbound path to alter, not adjusted for path aliases yet. It won't be adjusted for path aliases until all modules are finished altering it, thus being consistent with hook_url_inbound_alter(), which adjusts for all path aliases before allowing modules to alter it. This may have been altered by other modules before this one.

$options: A set of URL options for the URL so elements such as a fragment or a query string can be added to the URL.

$original_path: The original path, before being altered by any modules.

See also


Related topics

2 functions implement hook_url_outbound_alter()

Note: this list is generated by pattern matching, so it may include some functions that are not actually implementations of this hook.

locale_url_outbound_alter in modules/locale/locale.module
Implements hook_url_outbound_alter().
url_alter_test_url_outbound_alter in modules/simpletest/tests/url_alter_test.module
Implements hook_url_outbound_alter().
1 invocation of hook_url_outbound_alter()
url in includes/common.inc
Generates an internal or external URL.


modules/system/system.api.php, line 4353
Hooks provided by Drupal core and the System module.


function hook_url_outbound_alter(&$path, &$options, $original_path) {

  // Use an external RSS feed rather than the Drupal one.
  if ($path == 'rss.xml') {
    $path = 'http://example.com/rss.xml';
    $options['external'] = TRUE;

  // Instead of pointing to user/[uid]/edit, point to user/me/edit.
  if (preg_match('|^user/([0-9]*)/edit(/.*)?|', $path, $matches)) {
    global $user;
    if ($user->uid == $matches[1]) {
      $path = 'user/me/edit' . $matches[2];


David Strauss’s picture

This replaces custom_url_rewrite_outbound() from Drupal 6.

Anonymous’s picture

If you want to override an existing url_alias:

- change your $path as you need
- set $options['alias'] = $path;

This prevents url() in common.inc from running a drupal_lookup_path(), effectively changing the url_alias (temporarily, the url_alias is not changed).

quicksketch’s picture

This example makes it so that login and logout links everywhere on the site (that run through url() of course) always take the user back to the page that they clicked login or logout.

 * Implements hook_url_outbound_alter().
function mymodule_url_outbound_alter(&$path, &$options, $original_path) {
  // Always make login/logout links go to the current page.
  switch ($path) {
    case 'user/logout':
    case 'user/login':
      $options['query']['destination'] = $_GET['q'];
liquidcms’s picture

its unfortunate that, although this seems to imply that $options can be altered; this is not actually the case for most $options.

Only those handled by url() such as fragment and query may be altered. Since the l() function does not get the altered $options returned to it from the url() function you can't alter such $options as title or attributes (which is really annoying that these can't be altered, afaikt).

fonant’s picture

This is indeed most annoying: I would like to use this hook to add classes to a link, but even though $options is passed in by reference, altering $options['attributes']['class'] has no effect on the link.

scottatdrake’s picture

There is a handy Drupal 6 contrib module that provides this hook:

Dave Reid’s picture

Note that when changing $path, if this hook is being invoked from url() it will still attempt to do a lookup using drupal_get_path_alias() for $original_path which could overwrite your change. To prevent this call, also set $options['alias'] = TRUE which will skip the drupal_get_path_alias() call.

ginosuave’s picture

How can I make this hook apply only to links in the node content (i.e. exclude System and menu links)?

laboratory.mike’s picture

Here's an example from a project I'm working on. It prepends a country/language prefix to all URLs, and also has a built-in "exclude" list that excludes admin and file system URLs (otherwise it will write over file paths!).

This isn't the final form, of course, as we're currently working on an admin interface to replace hard-coded values like $c_list, $system_paths, and $exclude_paths. We're also working on caching for performance reasons, because this hook will touch every URL passed through url(), on every page load.

Also, I want to warn others that hook_url_outbound_alter on its own, when settings prefixes, can create an infinite loop if not set up correctly. In addition to a check to not set a prefix, it's also a good idea to have a hook_url_inbound_alter to remove the prefix (from Drupal's perspective, it's still present in the browser), and possibly do something with it. In our case, "country" is an OG context.

/* Helper function for using strpos with an array of strings.

function strposa ($haystack, $needles) {
	$bool = false;
	foreach ($needles as $needle) {
		if (strpos($haystack, $needle) !== false) {
			$bool = true;
	return $bool;

/* Implements hook_url_outbound_alter

function mymodule_url_outbound_alter(&$path, &$options, $original_path) { 
	//Break path into an array
	$arr = explode('/',$path);
	//Get country list and exclude paths
	$c_list = array('us','ca');
	$system_paths = array('admin', 'user', 'sites', 'system');
	$exclude_paths = array('node/add', 'sitemap');
	//If the path contains a system path in the first argument, or has an exclude path in the full path string, do not set a prefix.
	if (in_array($arr[0], $system_paths) || strposa($path, $exclude_paths)) {
		$options['prefix'] = '' ;
	//OK, it's not an excluded path. If the path doesn't have the prefix already, add a prefix.
	} else if (in_array($arr[0], $c_list) == false) {
		$c = $_COOKIE('Drupal_visitor_country');
		$l = $_COOKIE('Drupal_visitor_language');
		$options['prefix'] = $c . '/' . $l . '/';
		//Special case: remove "home" from the path if it's the home page, so that it's example.com/us/en instead of example.com/us/en/home.
		if (drupal_lookup_path('alias', $path) === 'home') {
			$path = '';
joey-santiago’s picture


i'm using a quite similar approach on a project. i now have changed all the links to use a path like language/language/alias (es: http://site.com/en/en/user). It works for links and even forms are now sent to the right URLs, but for some reason on some forms i get redirected to a simpler path: language/alias (es: http://site.com/en/user). Do you have the same issue?

laboratory.mike’s picture

I would suggest using drupal_set_message() or dpm() at the beginning and end of your function, to see what's being written in each case. Also, check whether you are setting prefixes elsewhere, such as i18n or locale.

In my own experience, there were underlying conditions that changed the behavior, such as the condition being met on some pages but not others. Also, I had set it up so hook_url_inbound_alter was removing the prefix internally, and the outbound_alter was adding it back, and this turned out to be unstable as certain URLs caused the outbound alter to not be called, or to be repeatedly called, leading to a redirect loop (en/en/en/en/en/en/en...). The key is to carefully parse $path so you get the exact behavior you expect, with no room for deviations.

Finally, feel free to contact me from the regular drupal.org site, as I am more likely to get the message in a timely manner. Hopefully it's all working for you now :)

laboratory.mike’s picture

Here's another example, this time looking at adding UTM values in a query string based on a user cookie. Google Analytics is doing away with its own UTM cookie, and AFAIK is recommending that others need to roll their own if they need to pass UTM values to other service.

function mymodule_url_outbound_alter(&$path, &$options, $original_path) {
	//$options['external'] limits this to External URLs, i.e. cases when we want to send UTMs to outside services. This can be limited further if needed.
	if ($options['external'] === TRUE) {
		//If empty, set the option as a blank array to be populated in the foreach loop.
		$options['query'] = empty($options['query']) ? array() : $options['query'] ;
		foreach (mymodule_retrieve_utm() as $key => $item) {
			$options['query'][$key] = $item;

function mymodule_retrieve_utm() {
	//mymodule_get_utm is a custom function that converts inbound URLs into cookie/session parameters. This should be set up to only run if the cookie is empty, as hook_url_outbound_alter and this function may be called dozens of times per page load.
	//build the return array
	$return = array();
	!empty($_COOKIE['Drupal_visitor_uTMCampaign']) ? $return['uTMCampaign'] = $_COOKIE['Drupal_visitor_uTMCampaign'] : $return['uTMCampaign'] = $_SESSION['uTMCampaign'];
	!empty($_COOKIE['Drupal_visitor_uTMMedium']) ? $return['uTMMedium'] = $_COOKIE['Drupal_visitor_uTMMedium'] : $return['uTMMedium'] = $_SESSION['uTMMedium'];
	!empty($_COOKIE['Drupal_visitor_uTMSource']) ? $return['uTMSource'] = $_COOKIE['Drupal_visitor_uTMSource'] : $return['uTMSource'] = $_SESSION['uTMSource'];
	!empty($_COOKIE['Drupal_visitor_uTMTerm']) ? $return['uTMTerm'] = $_COOKIE['Drupal_visitor_uTMTerm'] : $return['uTMTerm'] = $_SESSION['uTMTerm'];
	return $return;
kumkum29’s picture

if I use this hook and if I'm logged, it's ok and the hook is active on all the pages.
But, If I'm deconnected and If I visite the front page, this hook isn't active.
I want to disable the prefix lang on the home page for several subdomains (I use Domain Access). Here is my code :

function mymodule_url_outbound_alter(&$path, &$options, $original_path) {
		 // Get the current url	
		 global $base_url;
		 $url_args = explode('.', $base_url);
		 if ($url_args[0] != 'http://www') {
			$options['prefix'] = '';

Have you any idea to resolve this problem?

laboratory.mike’s picture

I would recommend starting with dpm(), print_r(), or drupal_set_message() with a message like 'active' for the first line of your function. This checks that the function is called.

Next, look at the $path variable, $options['prefix'], etc. to be sure that you have what you think you have. More than one module may be working with this data, so it's good to know where you are in the URL rewrite process. Perhaps your rewrite is called before i18n.

Finally, how does the page work with caching, and what does your path alias table look like? I had a bear of a time getting rid of "home" from my home page; I needed to remove it via both hook_url_outbound_alter and hook_url_inbound_alter, as "home" was added in two different contexts.

drpl’s picture


to change the admin or user default path in Drupal, I just do this:

 * Implements hook_url_inbound_alter
function custom_url_inbound_alter(&$path, $original_path, $path_language) {
  // give 404 for old admin path
  if (preg_match('|^admin(/{0,1}.*)|', $path, $matches)) {
    $path = '404'. $matches[1];
  // change new path to admin
  if (preg_match('|^control(/{0,1}.*)|', $path, $matches)) {
    $path = 'admin'. $matches[1];
  // same for user path, give 404 not found
  if ($path =='user') {
    $path = '404';
  // change the path to user
  if ($path =='access') {
    $path = 'user';

 * Implements hook_url_outbound_alter
function custom_url_outbound_alter(&$path, &$options, $original_path) {
  //check admin path and change it to other path
  if (preg_match('|^admin(/{0,1}.*)|', $path, $matches)) {
    $path = 'control'. $matches[1];
  if ($path == 'user') {
    $path = 'access';

I faced some issue with admin_menu module


JamesRobertson’s picture

If you wish to pass page 1's url to page 3 via page 2, this is a great solution.

I use this for the login page, user/login (page 2) which gives you the option to "Create new account", without this code, user/register (page 3) will not remember the original url (page 1)

I hope this makes sense.

 * Implements hook_url_outbound_alter().
function myModule_url_outbound_alter(&$path, &$options, $original_path) {
	switch ($path) {
		case 'user/register':
		case 'user/password':
			$parameters = drupal_get_query_parameters();
					$options['query']['destination'] = $parameters['destination'];


jigarius’s picture

Is there any D8 equivalent for it? I want to edit the URL for all taxonomy terms of a particular vocabulary and make them /catalog/products/{url-friendly-term-name}.

mvc’s picture

This is documented in the change record at https://www.drupal.org/node/2238759

panshulk’s picture

I am working on a d7 website, where i need to add utm parameters as query options . I am able to do it successfully using "$options['query']". But the url now appears to be like this :
What i want is to remove the trailing "/" before "?". Can anyone suggest a how to remove "/" from the urls ??