by Jeff EatonNovember 26, 2007

Hiding content from Drupal's search system

Drupal offers a variety of ways to integrate with the built-in search system, from connecting with third-party search systems to adding information to standard node content. In addition, Drupal's search system respects the access permissions on each piece of content -- users who can't access a particular node will never see it in the results of their searches.

What happens, though, if you want users to be able to access some content (user bio nodes, for example) if they navigate to it directly, but don't want that content to appear in search results? Drupal doesn't offer any way to do that by default, but your custom module can use the same behind-the-scenes hooks used by the security system to control exactly what search results are presented to users. The magic happens in hook_db_rewrite_sql(). Whenever Drupal code calls the db_rewrite_sql() function to pull information from the database, other modules can use hook_db_rewrite_sql() to intercept the SQL call and add additional filters to the query. That's how modules like Organic Groups restrict access to content based on group membership: when queries pull information from the node table, it compares the groups the node is associated with to the groups the current user belongs to.

Intercepting the queries used by the search system takes a bit of extra work, though. We'll take a look at some example code and see how the funky bits work.

function your_module_db_rewrite_sql($query, $primary_table, $primary_field, $args) {
  if ($query == '' && $primary_table == 'n' && $primary_field == 'nid' && empty($args)) {
    $excluded_types = variable_get('your_module_types', array());
    if (!empty($excluded_types)) {
      $where = " n.type NOT IN ('". join("','", $excluded_types) ."') ";
      return array('where' => $where);

Modules that implement hook_db_rewrite_sql() receive a couple important pieces of information about each query. The most important is the 'primary table' parameter -- you don't want to add a SQL WHERE filter intended for nodes when the primary table is 'user', for example. Due to some curious code inside the node module, however, the query that's passed in is treated as empty. While that's a pretty big violation of Drupal's own coding standards, it makes it easy to intercept just the node search queries.

So, we first check to see whether the incoming query is empty, the table is 'n', and the primary field is 'nid'. If those conditions are matched, the rest is easy: we grab a list of node types that we want to hide from the search results and build a WHERE condition that hides them. That's it!

To make things a bit cleaner, we can add some configuration options.

function your_module_search($op = 'search') {
  if ('admin' == $op) {
     $form = array();
     $form['your_module_types'] = array(
       '#type'           => 'select',
       '#multiple'       => TRUE,
       '#title'          => t('Exclude Node Types'),
       '#default_value'  => variable_get('your_module_types', array()),
       '#options'        => node_get_types('names'),
       '#size'           => 9,
       '#description'    => t('Node types to exclude from search results.'),
     return $form;

function your_module_form_alter($form_id, &$form) {
  if ('search_form' == $form_id) {
    $excluded_types = variable_get('your_module_types', array());
    $types = array_map('check_plain', node_get_types('names'));
    foreach($excluded_types as $excluded_type) {
    $form['advanced']['type']['#options'] = $types;

What do the two code snippets above do? The first one -- hook_search() -- adds an extra form field to the search administrative settings page. It allows site admins to choose which kinds of content should be hidden. The original snippet we used to do the SQL rewrite will use that list of types to build its WHERE query.

The second snippet alters the advanced form that users see when they search for content on your site. Normally, it shows a list of all the site's content types and lets users choose what they want to see. This implementation of hook_form_alter(), though, removes options from that list if the admin has listed the content types as hidden. That ensures that users will never see options that are impossible to use.

That's it! The same technique can be used to hide content from specific users, content posted on Wednesdays, or any other criteria that's needed.