Search code examples
wordpresswordpress-rest-api

WP REST API returns HTML instead of JSON


An endpoint of https://example.com/wp-json/wp/v2/pages/123 returns a combination of HTML and JSON format such as:

<div class="my-class">HTML Content</div>
{ "id" : 123, ... }

There is an excerpt filter in functions.php that set the outputted HTML, in this line:

function my_custom_filter(){
  echo '<div class="my-class">HTML Content</div>';
}
add_filter ('the_excerpt', 'my_custom_filter' );

How to prevent such filter from intervening with the JSON response?


Solution

  • If you can modify the filter, then you can detect the REST API inside the filter function, like this:

    function my_custom_filter(){
        if (! defined("REST_REQUEST")) {
            echo '<div class="my-class">HTML Content</div>';
       }
    }
    add_filter ('the_excerpt', 'my_custom_filter' );
    

    The constant REST_REQUEST is only defined inside a request that was handled by the REST API. So, with the code above, if a request is being handled by the normal WP request cycle, the constant will not be defined, so the if statement we added will evaluate as true and the echo will occur like normal. If the constant is defined, then the if will evaluate as false and the filter will not add any output to the response.

    See https://wpseek.com/constant/rest_request/

    UPDATE: while the code above will work, I think we can do better.

    The problem is that if you have many filters, every one of them needs to duplicate the if statement and your code become littered with checks for REST_REQUEST, which

    • is repetitive (not DRY)

    • not the concern of the filters themselves

    • untidy

    Instead, we can introduce the REST_REQUEST checks earlier in the cycle by making it a concern of the code that adds the filter.

    This would look like:

    function my_custom_filter(){
        echo '<div class="my-class">HTML Content</div>';
    }
    
    if (! defined("REST_REQUEST")) {
        add_filter ('the_excerpt', 'my_custom_filter' );
        //add other HTML filters here
    } else {
        //attach REST API actions
    } 
    

    This seems like a small change (if you catch it early, when the number of filters is small), but it potentially brings some big advantages:

    • it keeps the REST_REQUEST checks all in one place: maintainability

    • the filters don't need to know or care about whether it's a REST_REQUEST or not - if they get called, they're good to go. Keeping it simple.

    • you can potentially do other things like logging or role-based access checks in that place: extensibility

    It's a relatively easy change to make early on in your application, but becomes more difficult and error prone as you add further filters and actions. Always better to do it right the first time!