Software, Development, and WordPress

Resolving wp_redirect and the “Headers Already Sent” message

I’ve been working on building a web application in WordPress on which I’m implementing a set of rewrite rules to introduce RESTful routing into the application.

Once the application is done, I hope to provide a significantly more in-depth post on how I built it, but in the mean time I figured I’d cover how I’m handling certain challenges that I’ve faced in development.

In this case, I needed to fire a call to wp_redirect after a certain event happened, but kept getting the PHP error:

Cannot modify header information - headers already sent by (output started ... )

Here’s how I ended up resolving the wp_redirect headers already sent message.

A Word About Routes

This is an oversimplification of what I’m doing, but I essentially have various models in the application. In this case, I was working with a person model.

Through my rewrite rules, I can perform the following actions:

  • person/all lists all the people
  • person/add adds a new person to the application
  • person/update/1 where 1 is the ID of the user)
  • person/delete/1 where 1 is the ID of the user and asks the user to confirm deletion
  • person/destroy/1 where 1 is the ID of the user and where the user is actually removed from the database

For anyone coming from a Rails or RESTful background, this should be familiar; otherwise, it should be clear enough.

The Problem of Destroy

So the general flow of control of control is like this:

  • The user selects the person they want to delete
  • They are directed to the delete action where they are prompted to confirm or cancel the action
  • If they confirm, they are directed to the destroy action
  • When a success destroy occurs (that is, the user is successfully deleted), they should be be redirected back to the person/all view.

Everything was good except once the destroy action completed, I was seeing the usual PHP header message:

Cannot modify header information - headers already sent by (output started ... )

The problem is the page is beginning to render text and then my call to wp_redirect was attempting to load another page prior to finishing the initial page’s load.

This isn’t good.

Of Redirects and Output Buffers

To fix the problem of being unable to redirect until a page has actually loaded, I added a hook on the init action that will actually buffer output using ob_start.

This will allow for a redirect to occur before the initial page has finished loading.

The output buffer function looks like this:

function app_output_buffer() {
} // soi_output_buffer
add_action('init', 'app_output_buffer');

And the function for destroying the person looks like this (which is called in the context of the person’s destroy view – or page, in WordPress terms).

function app_destroy_person( $person_id ) {

	// Include the necessary library to delete a person
	include_once( 'wp-admin/includes/user.php' );
	wp_delete_user( $person_id );

	// Redirect back to the Person listing
	wp_redirect( app_get_permalink_by_slug( 'all', 'person' ) );

} // end app_destroy_person

As always, there may be a better way. If you know of one, then please share it in the comments.


  1. Mario Peshev

    In addition to that, you could verify the $_SERVER array for specific incoming data if you want to keep the buffering only for specific pages.

    I haven’t tried it myself, but maybe the template_include hook could server for the same purpose by providing empty template (therefore no headers are sent before that).

    • Tom McFarlin

      Indeed – and thanks for sharing this.

      I’m certainly not above using $_SERVER variables, but if the WordPress API offers the ability to do something, I try to use it first.

      If it fails, that’s when I fallback to the features of PHP.

      • Mario Peshev

        If you hook to a point too early for admin_url and other path-related variables, there are not that many options unfortunately. $_SERVER is tricky, especially when the default flow requires some back and forward communication to other servers (therefore the path is passed via redirect_uri variables instead of the domain itself), but the technical limitations of the artificial intelligence for reading domain name based on a random server’s fail can’t be overcome

  2. Tia Wood

    Thank you so much for providing this solution. I did not realize wp_redirect was like header() in the sense that it had to load before the output. Funny thing is wp_redirect worked on my local server but not the live testing server. I tried to get around it using javascript but the actual redirect was too slow. This was a perfect solution. Thank you!

  3. Paul Marr Online Store

    thanks for the help.

  4. Marcio Dias

    Congrats, works perfectly!

    Thanks for the help.

  5. Henry

    Are there any implications of doing this on every page rather than conditionally as and when you need it?

    • Tom

      I think I misunderstand your question: do you mean flushing the output buffer on every page?

  6. Girish

    Thanks , it helps me to redirect in custom plugin .
    Can ob_start() conflict with any other wordpress functionality?

    • Tom

      In some ways, maybe. Let’s say some other function has called it but it has not yet called ob_end_flush. In that case, you’re calling ob_start() before flushing the output buffer.

      I haven’t personally seen this happen, but I know that it can be of some issue.

  7. Girish

    Thanks , appreciate your efforts

  8. Defkon1

    This tip saved me a day of work. Thanks!

  9. Bhumi


    Thanks for the post, it’s useful as always!

  10. Yudhistira Mauris

    Hi Tom,

    Thanks for the tip. I usually add ob_start() right before the wp_redirect() without the init hook. It usually works well. I don’t know if there might be a side effect.


    • Yudhistira Mauris

      I just found a case where I had to add ob_start() to init hook. It wouldn’t work if I added it right before wp_redirect(). Do you know what might cause it?

      • Tom

        I’m not sure. I’ve never really done any work with output buffering and redirects. I’m not saying there is a reason, I’m just saying that I personally dunno :).

    • Tom

      Thanks for adding this to the comment – I appreciate it!

  11. Sibhi

    Wonderful! You have helped me and saved my time a lot, So grateful post. Thank You!

  12. Ashvin

    Great,works perfectly!

  13. Rajan

    Wow Its working great work

  14. Dirk

    Thanks Tom. Not sure why this works if there’s no ob_end and why it doesn’t mess up the rest of your admin pages, but hey… it does seem to work.

  15. Antony

    Fabulous thanks! By combining with ob_clean() you can also use it to send download headers if required.

Leave a Reply

© 2020 Tom McFarlin

Theme by Anders NorenUp ↑