WordPress Development Stack Exchange is a question and answer site for WordPress developers and administrators. Join them; it only takes a minute:

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

I need to build a "export as .csv" functionality into my plugin. Problem is: i get the header already sent warning, because by the time my "export" function is called, Wordpress has already started rendering.

The way i set it up is via a small form in the Settings screen, which action attribute points to a file (download.csv.php) in my plugin folder, that should not be shown, just prompt a file download dialog for the csv dump.

Here is my code.

In my plugin Settings screen:

<form method="post" id="download_form"  action="<?php echo plugins_url( 'download.csv.php' , __FILE__ ); ?>">
            <input type="hidden" name="download" value="<?php echo get_home_path(); ?>" />
            <input type="submit" name="download_csv"  class="button-primary" value="<?php _e('Download the log (.csv)', $this->localizationDomain); ?>" />

And the download.csv.php file:

$core = $_POST['download'].'wp-load.php';

if(isset($_POST['download']) && is_file($core)){

    require_once( $core );

    global $wpdb;

    $rows = $wpdb->get_results('SELECT * FROM `'.$this->dbName.'`;',ARRAY_A);


        $CSV = new CSVMaker();

        $CSVHeader = array();
        $CSVHeader['id'] = "ID";
        $CSVHeader['when'] = "WHEN";
        $CSVHeader['who'] = "WHO";
        $CSVHeader['description'] = "DESCRIPTION";
        $CSVHeader['type'] = "TYPE";

        foreach($rows as $r){
        $file_name = plugin_dir_path(__FILE__).'data.csv';
        header("Expires: Mon, 1 Apr 1974 05:00:00 GMT");
        header("Last-Modified: " . gmdate("D,d M YH:i:s") . " GMT");
        header("Cache-Control: no-cache, must-revalidate");
        header("Pragma: no-cache");
        header("Content-type: application/CSV");
        header("Content-Disposition: attachment; filename=$file_name.csv");
        echo $CSV->buildDoc;
        return ob_get_clean();


What am i doing wrong?

share|improve this question
up vote 2 down vote accepted

The start of what you're doing wrong can be explained by this quote:

which action attribute points to a file (download.csv.php) in my plugin folder, that should not be shown, just prompt a file download dialog for the csv dump.

and this code:

$core = $_POST['download'].'wp-load.php';
if(isset($_POST['download']) && is_file($core)){
    require_once( $core );

What you're basically doing is a) stepping outside of the WordPress environment and accessing some part of your plugin directly and b) then trying to load the WordPress environment from inside that file (and doing it in an extremely insecure manner, I might add).

Instead of doing that, it's better to stay inside the WordPress environment to begin with and to override the output to be the way you want it to be.

A better way would be to stay in the admin, hook to admin_init, and detect when you need to get your CSV output and return that instead.

So for your form, do something like this:

<form method="post" id="download_form" action="">
            <input type="submit" name="download_csv" class="button-primary" value="<?php _e('Download the log (.csv)', $this->localizationDomain); ?>" />

Notice no action is used here. This means it's submitting back to your same admin page, with no changes. Now, you can detect that in whatever function you have connected to the admin_init action hook, like so:

global $plugin_page;
if ( isset($_POST['download_csv']) && $plugin_page == 'whatever' ) {
    echo "HELLO"; die;

The $plugin_page global will be set to the page=whatever you have in your normal plugin's settings screen. Instead of echoing "HELLO" like I did here, call a function in your plugin to generate and output that CSV properly, headers and all, and then die. Or something to that effect.

Note that this is simplistic, and may be insecure. You may also want to do a nonce and a capabilities check here, to ensure that the user is allowed to download this CSV and intended to do so.

share|improve this answer
Awesome. I knew i was doing it wrong, but your explanations got me going. Thank you!! – pixeline Aug 27 '12 at 23:31

Your Answer


By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.