7 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 4339
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];


This replaces custom_url_rewrite_outbound() from Drupal 6.

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).

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) {
$options['query']['destination'] = $_GET['q'];

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).

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

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.

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

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;

/* Implements hook_url_outbound_alter

function rl_int_url_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 = '';


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?

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;

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();
$_COOKIE['Drupal_visitor_uTMCampaign']) ? $return['uTMCampaign'] = $_COOKIE['Drupal_visitor_uTMCampaign'] : $return['uTMCampaign'] = $_SESSION['uTMCampaign'];
$_COOKIE['Drupal_visitor_uTMMedium']) ? $return['uTMMedium'] = $_COOKIE['Drupal_visitor_uTMMedium'] : $return['uTMMedium'] = $_SESSION['uTMMedium'];
$_COOKIE['Drupal_visitor_uTMSource']) ? $return['uTMSource'] = $_COOKIE['Drupal_visitor_uTMSource'] : $return['uTMSource'] = $_SESSION['uTMSource'];
$_COOKIE['Drupal_visitor_uTMTerm']) ? $return['uTMTerm'] = $_COOKIE['Drupal_visitor_uTMTerm'] : $return['uTMTerm'] = $_SESSION['uTMTerm'];

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?