Last updated August 31, 2012. Created on August 31, 2012.
Edited by NancyDru. Log in to edit this page.

I recently was tasked with creating a report of how many people signed up for the site by date. The query was simple, but creating the file was a bit of a challenge.

 * User sign up report, output as a CSV.
function mymodule_signups_csv() {
  // Prevent Devel from messing us up.
  $GLOBALS['devel_shutdown'] = TRUE;
  // Set the headers to indicate this is a CSV file.
  header('Content-type: text/csv; charset=UTF-8');
  header('Content-Disposition: attachment; filename=user_signups.csv');
  header('Pragma: no-cache');
  header('Expires: 0');

  // Create a file.
  $output = fopen('php://output', 'w');

  // Column names.
  fputcsv ($output, array('Date', 'Count'));

  $query = "SELECT DATE(FROM_UNIXTIME(created)) AS joined, COUNT(*) AS number FROM {users} "
    . "WHERE status = 1 "
    . "GROUP BY DATE(FROM_UNIXTIME(created)) "
    . "ORDER BY created DESC ";
  $result = db_query($query);

  // Loop through the rows.
  foreach ($result as $row) {
    fputcsv($output, (array) $row);


To get to this code was just a matter of creating a MENU_CALLBACK and adding a link to an existing page.

Note the 3rd line. Devel outputs its stuff after the </html> tag, so Excel saw that as data. That simple statement just turns Devel off for this single page load.

Looking for support? Visit the forums, or join #drupal-support in IRC.


leenyx’s picture

Hi, this looks very helpful but please can you explain how to get to this code? Can you give us an example of the MENU_CALLBACK and the link to the existing page?


NancyDru’s picture

function mymodule_reporting_permission() {
  return array(
    'access mymodule reports' => array(
      'title' => t('Access reporting section'),
      'description' => t('View reports on mymodule activity.'),

function mymodule_reporting_menu() {
  $items = array();

  $items['mymodule-reporting/signups_csv'] = array(
    'page callback' => 'mymodule_reporting_signups_csv',
    'access arguments' => array('access mymodule reports'),
    'type' => MENU_CALLBACK,

  return $items;
 * Downloadable reports.
function mymodule_reporting_downloads() {
  $output = '<div id="mymodule-reporting">';
  $output .= '<div id="mymodule-reporting-downloads"><br />';
  $links = array();

  // Add a link to download sign up stats.
  $links['signup-download'] = array(
    'title' => t('Download registration statistics'),
    'href' => 'mymodule-reporting/signups_csv',

  $output .= theme('links', array('links' => $links));

  $output .= '</div>  <!-- /mymodule-reporting-downloads -->';
  $output .= '</div>  <!-- /mymodule-reporting -->';
  return $output;

There is, of course, a lot of other stuff missing from our actual implementation.