Allowing admin-ajax.php to receive “application/json” instead of “x-www-form-urlencoded”

it seems the latter only accepts x-www-form-urlencoded

That’s not completely true.

WordPress admin-ajax.php takes the action from $_REQUEST['action'] and $_REQUEST is equal to:

array_merge($_POST, $_GET);

But what many people don’t realize is that $_GET in PHP is not the data was sent to page using HTTP GET method, in fact, you can use whatever HTTP method (POST, PUT, DELETE…)
and whatever content type, and $_GET will always contain data you pass to url query string.

It means that if you send a request to wp-admin/admin-ajax.php?action=awesome_action “awesome_action” is always reached, no matter the HTTP method you use and no matter the HTTP content type.

In short, if you are able to send the action argument with the url, you can use admin-ajax.php to send whatever JSON data and whatever HTTP method, then is your problem handle them.

However that’s quite easy using php://input stream.

Example

First of all I’ll write a function that uses curl to send an HTTP request with JSON content type (because I don’t want to write Angular code here):

/**
 * @param string $url  Url for for the request
 * @param string $data JSON-encoded data to send
 */
function mySendJson($url, $data)
{
    $ch = curl_init($url);
    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "PUT"); // PUT HTTP method
    curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_HTTPHEADER, array(
        'Content-Type: application/json',           // JSON content type
        'Content-Length: ' . strlen($data))
    );
    return curl_exec($ch);
}

Pretty easy.

Now, just for testing purpose, I’ll put a function in frontend footer that sends a HTTP request with JSON content type using the function above, and expects some JSON back:

add_action('wp_footer', function() {

    $ajax_url = admin_url('admin-ajax.php?action=json-test');

    // some dummy json data
    $data = array("name" => "Giuseppe", "country" => "Italy");
    $json = json_encode($data);

    // Using curl we simulate send HTTP request with json content type
    // in your case is Angular that perform the request
    $json_response = mySendJson($ajax_url, $json);

    echo '<pre>';
    print_r(json_decode($json_response, true));
    echo '</pre>';
});

Now, when admin-ajax.php is requested it will look for any callback attached to 'wp_ajax_json-test' hook (or 'wp_ajax_nopriv_json-test') for non-logged users.

Let’s add a custom function to those hooks:

add_action('wp_ajax_json-test', 'myWpAjaxTest');
add_action('wp_ajax_nopriv_json-test', 'myWpAjaxTest');

The function myWpAjaxTest() will be called by WordPress when the HTTP request with JSON content type is sent to admin-ajax.php.

Let’s write it:

function myWpAjaxTest() {
    // Retrieve HTTP method
    $method = filter_input(INPUT_SERVER, 'REQUEST_METHOD', FILTER_SANITIZE_STRING);
    // Retrieve JSON payload
    $data = json_decode(file_get_contents('php://input'));

    // do something intersting with data,
    // maybe something different depending on HTTP method

    wp_send_json(array(  // send JSON back
      'method'        => $method,
      'data_received' => $data
    ));
}

Thanks to php://input (docs) last function

  • parses the JSON data that was sent to admin-ajax.php
  • manipulates it
  • sends back a JSON response (because wp_send_json uses json content type)

As final result, if you look at theme footer, you’ll find something like:

Array
(
    [method] => PUT
    [data_received] => Array
        (
            [name] => Giuseppe
            [country] => Italy
        )

)

Leave a Comment