I'm doing an export function in my plugin but I cannot force a direct download. I get the error:

Cannot modify header information - headers already sent

Apparently at some point before my function fires WordPress has already started printing in the body of the response so I can't change the headers when my function reaches. Is there any way I can go around this and add my headers so I can force a direct download?

I've tried de-registering all scripts on admin_init when the words "direct-download" are found in the URL but apparently the scripts is not the only thing WordPress is printing out into the response body.

Is there a standard way of achieving a direct download in the admin section?

The plugin I'm working on has an MVC format. My Export button is just a simple link (<a>) that leads to my action method:

public function exportRecordsToExcelAction()
    // Get parameters
    // Get columns              
    // Get records
    // Excel stuff

    header('Content-type: application/vnd.ms-excel');
    header('Content-Disposition: attachment; filename="Records.xlsx"');


The headers already sent errors refer to line 958 of wp-includes/script-loader.php. This is where WordPress starts printing out to the response body and ends my ability to change the headers. It's not the only place that does this so obviously trying to circumvent this function does not help in the least.

The annoyingly abstracted architecture that I'm working with is the following.

Folder structure:

            add_submenu_page() hooks

How my we reach my function in AccountController.php:

When I execute any URL in the plugin pages an object in a file in the pluginFolder first executes a function that parses the request URL and from it determines the controller name and action method. Once they're parsed we have the method name in a variable $method and the controller name in a variable called $controller. Then it instantiates the controller object using a factory method like so: $controller = self::getController($controller);. Since now it has the proper controller object and it knows the name of the action method it just does this: $controller->$method(); and the action method (my function in the first code) is executed.

At the moment I can't see how changing any of this would help me since all of this is executed after the headers have already been sent. Maybe the only way to force a download is for me to skip this MVC action method stuff this time and just create a regular function that gets executed when I click the Export button?

  • My guess is you're trying to send the download from within your add_submenu_page callback? Post some relevant code and I'll steer you clear. Commented Jan 15, 2016 at 9:47
  • @TheDeadMedic I've edited my question.
    – Alternatex
    Commented Jan 15, 2016 at 9:58
  • Need moar. When is exportRecordsToExcelAction called? Can you show your hooks/filters/menu code. Commented Jan 15, 2016 at 10:01
  • @TheDeadMedic The plugin I'm working on is halfway developed by some other company and apparently before my function even reaches the page (header, footer) is almost completely drawn into the response body. I guess the only way to fix it is to discuss it with them since there's a LOT of code that gets executed before my function gets called.
    – Alternatex
    Commented Jan 15, 2016 at 10:29

1 Answer 1


It's what I thought - you're trying to send the file from the display callback, by which time it's too late. Look for the add_*_page() call that registers the menu item in the admin sidebar - the return value of this function call is the name of the hook you can use to attach the download function:

$hook = add_submenu_page( $parent, $title, $menu_title, $cap, $menu_slug, $callback );

if ( $hook ) // Will be false if current user does not neccessary permissions
    add_action( "load-$hook", $download_callback );
  • I understood what's happening. Isn't there any way I can force a download from a subpage without doing it in the same place where the add_submenu_page hooks are? The problem is that I can't call the download function there because in order to do that I'd need to reference a thousand classes (db classes, extended classes, controller classes etc.).
    – Alternatex
    Commented Jan 15, 2016 at 12:11
  • Can you not use a global to reference the instantiated class? I understand your frustration but it's really, really difficult to help when I've seen very little code and have almost no idea about the architecture/functioning of your plugin. Commented Jan 15, 2016 at 12:16
  • Added some additional info.
    – Alternatex
    Commented Jan 15, 2016 at 12:43
  • The very last paragraph in your updated question seems the way to go. Sorry I can't be of any real help. If you were willing to share your codebase on github/similar I could possibly offer an alternative solution Commented Jan 15, 2016 at 12:50
  • Thank you for your help. At least now I know what I'm working with :)
    – Alternatex
    Commented Jan 15, 2016 at 12:51

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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