Custom view mode issues My goal is to switch between different view modes based on the node path (pattern). In my case, the patterns would be:


dossier/node-alias => default template
dossier/node-alias/cover => cover template
dossier/node-alias/abstract => abstract template


I have created the custom view modes with hook_entity_info_alter (simplified here to only one custom view mode):

/**
 * Implements hook_entity_info_alter
 */
function dossier_config_entity_info_alter(&amp;$entity_info) {

  $entity_info['node']['view modes']['cover'] = array(
    'label' =&gt; t('Cover view mode'),
    'custom settings' =&gt; TRUE,
  );
}


Here is how I declared the path pattern in hook_menu:

/**
 * Implements hook_menu
 */
function dossier_config_menu() {

  $items = array();

  // Cover entity view mode
  $items['dossier/%/cover'] = array(
    'title' =&gt; 'Default title', // overridden in template.php preprocess_node function
    'page callback' =&gt; '_dossier_config_switch_viewmodes',
    'page arguments' =&gt; array(1, 2), // arguments from uri are 1 =&gt; node alias and 2 =&gt; cover
    'access arguments' =&gt; array('access content'),
    'type' =&gt; MENU_CALLBACK,
  );

  return $items;
}


And here is my custom callback:

function _dossier_config_switch_viewmodes($alias, $view_mode) {

  // Get the node path based on the alias
  $path = drupal_lookup_path('source', 'dossier/' . $alias);

  // Return the node if the path exists
  if ($path !== FALSE) {

    $node = menu_get_object("node", 1, $path);
    $node_view = node_view(node_load($node-&gt;nid), $view_mode);

    return array(
      'content' =&gt; array(
        'node' =&gt; $node_view,
      ),
    );
  }

  return drupal_not_found();
}


It works. However, when looking at these nodes in the custom view modes, I lose the node edition tabs (View, Edit, Translate, etc.) and I also seem to lose all the standard classes that are added to the &lt;body&gt; such as node-type-xxx or page-node-12345 etc.

I believe that this is a normal behavior with such a custom callback in hook_menu (?). So what would be the right way to achieve what I want?

Any help on that will be highly appreciated! Cheers!

Edit: I forgot to mention that of course I have 3 different node templates and that I have added the theme_hook_suggestions accordingly in my theme template.php.
The path for your additional pages should be node/%node/abstract and node/%node/cover with something like

$items['node/%node/abstract'] = array(
  'title callback' =&gt; 'node_page_title',
  'title arguments' =&gt; array(1),
  'page callback' =&gt; 'MODULE_node_view',
  'page arguments' =&gt; array('abstract', 1),
  'access callback' =&gt; 'MODULE_node_view_access',
  'access arguments' =&gt; array('abstract', 1),
  'type' =&gt; MENU_LOCAL_TASK,
);

$items['node/%node/cover'] = array(
  'title callback' =&gt; 'node_page_title',
  'title arguments' =&gt; array(1),
  'page callback' =&gt; 'MODULE_node_view',
  'page arguments' =&gt; array('cover', 1),
  'access callback' =&gt; 'MODULE_node_view_access',
  'access arguments' =&gt; array('cover', 1),
  'type' =&gt; MENU_LOCAL_TASK,
);


If you don't want your additional pages to be available as tabs (like View, Edit and Translate), you can probably use 'type' =&gt; MENU_CALLBACK. By doing so, you ensure Drupal knows these pages are below the normal node view page.

MODULE_node_view($view_mode, $node) and MODULE_node_view_access($view_mode, $node) are your page and access callbacks. They receive the $node already loaded (thanks to the %node in the menu item path). If you don't have specific access rules (such as restricting access to only certain content type), you can replace the access callback and arguments by node_access and array('view', 1).

Given the question, your likely want to automatically create aliases for your nodes so you can use paths like dossier/node-alias/cover instead of things like use node/1234/cover. You can implement hook_path_insert() and hook_path_delete() to automatically create and delete abstract and cover aliases for any aliased node.

/**
 * Implements hook_path_insert().
 */
function MODULE_path_insert($path) {
  // If this is an alias to a node view page
  if (preg_match("/^node\/\d+(\/view)?$/", $path['source'], $matches)) {
    // Create aliases for the supported suffixes.
    foreach (array('/abstract', '/cover') as $suffix) {
      $new_path = array_merge($path, array(
        'source' =&gt; $matches[0] . $suffix,
        'alias' =&gt; $path['alias'] . $suffix
      ));
      unset($new_path['pid']);
      path_save($new_path);
    }
  }
}

/**
 * Implements hook_path_delete().
 */
function MODULE_path_delete($path) {
  // If this is an alias to a node view page
  if (preg_match("/^node\/\d+(\/view)?$/", $path['source'], $matches)) {
    // Delete aliases for the supported suffixes.
    foreach (array('/abstract', '/cover') as $suffix) {
      $new_path = array_merge($path, array(
        'source' =&gt; $matches[0] . $suffix,
        'alias' =&gt; $path['alias'] . $suffix
      ));
      unset($new_path['pid']);
      path_delete($new_path);
    }
  }
}