Question

Crosspost from StackOverflow

I've looked at multiple other questions about this warning (Headers already sent...etc) and this thorough explanation, but I'm not finding a solution for the circumstances I'm dealing with:

  1. I have a custom Wordpress REST API endpoint that returns the output of get_template_part. When I just call get_template_part normally on a page, there's no warning. It only appears when it runs in register_rest_route's callback.
  2. I am also fetching data from an external API using this library. The error started when I started making these requests. I've tried making the request both inside and outside the template part, the latter either within register_rest_route's callback or as an init action.
  3. The line that triggers the error is an img tag, where I'm echo-ing a URL as the src using data from the external API response. There are other echo calls all over this template, so I doubt that's the issue.
  4. The line in question actually works fine and does its job. I just need to get rid of the accursed warning.

Code:

Inside functions.php:

<?php

  //the api endpoint declaration
  add_action( 'rest_api_init', function () {
    register_rest_route(
        'theme/v2',   // namespace
        '/homepage',  // route
        array(                  // options
            'methods'  => 'GET',
            'callback' => 'build_home',
        )
    );
  });

  //the callback for the wordpress api
  function build_home() {

    // this is the external API request, using their PHP library. 
    // I linked the library above. The request returns an object with a bunch of data
    include_once(get_template_directory()."/arena/arena.php");
    $arena = new Arena();
    $page = $arena->set_page(); 
    $per = 8; 
    $slug = 'concepts-bo-6ajlvpqg'; 
    $channel = $arena->get_channel($slug, array('page' => $page, 'per' => $per));

    //I'm passing the response from the external API (in $channel) to get_template part
    //get_template_part is then the output for the wordpress API request
    get_template_part('custom-home-template',null,array('channel'=>$channel));
 }

 ?>

Inside template part:

<?php
  $channel=$args["channel"];
  ?>

  <div id="concepts-box" class="bodycontent reg-width">
    <?php foreach ($channel->contents as $item) { if($item->class=="Image"){
      $img=$item->image["square"]["url"];
      ?>
    <div class="concept-wrapper">
      <!-- the line below is the one that triggers the error -->
      <img lozad src="<?=$img; ?>">
    </div>
    <?php }} ?>
  </div>

Full warning:

<b>Warning</b>:  Cannot modify header information - headers already sent by (output started at /filepath/wp-content/themes/theme/custom-home-template.php:63) in <b>/filepath/wp-includes/rest-api/class-wp-rest-server.php</b> on line <b>1372</b><br />
Was it helpful?

Solution

I have a custom Wordpress REST API endpoint that returns the output of get_template_part

Note that the REST API expects your endpoint callback to always return the response (e.g. the template part output in your case), and not echo it or anything else. And after the callback is called, the REST API will send some headers, by default an Allow header and then any extra headers from your response — see WP_REST_Server::serve_request(), particularly lines #440 and #472.

So because of that, you should not echo anything in your callback, because for example echo 'foo'; header( 'Allow: GET' ); will cause the "headers already sent" error because the echo started the output, hence PHP issues a warning if you try to send a (HTTP) header afterwards.

Therefore, despite you said, "The error started when I started making these requests.", I'm pretty sure the problem is with your call to get_template_part() which (despite the name) actually simply loads the template part and if successful, the function returns nothing.

And if the template part echo something (which is what happens in most cases) just like yours as in the question, then that will result in the "headers already sent" warning. Try commenting out the get_template_part() call and see if the warning goes away. :)

But even if not, please ensure your callback is not echoing anything because doing so will invalidate the JSON response (both header and content) which is the default response type returned by the REST API endpoint. However, if you need to echo something, then you would want to use output buffering like so in your case:

function build_home() {
    ob_start();

    // ... your code.
    get_template_part( ... your code ... );
    $data = ob_get_clean();

    // The array key ("html") below can be any other name that you prefer.
    return new WP_REST_Response( array(
        'html' => $data,
    ) );
}

And please always set the permission_callback for your endpoint, because otherwise, you'd get a (_doing_it_wrong()) notice like so:

The REST API route definition for theme/v2/homepage is missing the required permission_callback argument. For REST API routes that are intended to be public, use __return_true as the permission callback.

So for example, your code would look like so:

register_rest_route(
    'theme/v2',  // namespace
    '/homepage', // route
    array(       // options
        'methods'             => 'GET',
        'callback'            => 'build_home',
        'permission_callback' => '__return_true', // always set a permission callback
    ),
);

And BTW, if you want a different response type (i.e. Content-Type) for your endpoint, then you can use the rest_pre_serve_request hook — see an example in my answer here.

Licensed under: CC-BY-SA with attribution
Not affiliated with wordpress.stackexchange
scroll top