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.

26 Replies to “Resolving wp_redirect and the “Headers Already Sent” message”

  1. 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).

    1. 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.

      1. 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. 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!

    1. 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.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.