All AJAX requests return a 400 error

I have not tested your code, but one problem I noticed is the contentType property in your $.ajax() call:

contentType: 'application/json; charset=utf-8',

because that way, (from the PHP side) the action (i.e. ajax_load_more) is not available in $_REQUEST['action'] which WordPress uses to determine the AJAX action being called, and when the action is not known, WordPress throws the error 400 Bad Request, which in your case translates to “unknown AJAX action”.

You should just omit the contentType property, or don’t set it to a JSON content type string.

Also, although you’re just testing in ajax_load_more(), make sure to return a proper JSON response/string because your dataType is json.

I hope this helps you. 🙂

Additional Notes

From the jQuery.ajax() documentation:

  1. dataType (default: Intelligent Guess (xml, json, script, or html))

    The type of data that you’re expecting back from the server. If none
    is specified, jQuery will try to infer it based on the MIME type of
    the response

  2. contentType (default: 'application/x-www-form-urlencoded; charset=UTF-8')

    When sending data to the server, use this content type. Default is
    “application/x-www-form-urlencoded; charset=UTF-8”, which is fine for
    most cases. If you explicitly pass in a content-type to $.ajax(),
    then it is always sent to the server (even if no data is sent).

So dataType is the content type (MIME) of the content from the AJAX response, whereas contentType is the content type of the content you are sending to the PHP/server-side function (which is ajax_load_more() in your case).

UPDATE

So by using the wrong content type the headers become unreadable?

No it is not about the headers become unreadable – (request) headers are always readable in PHP. It’s just that request payload (in JSON) is not supported by the WordPress AJAX – only the request body. But you can of course, send form field data/value as JSON – for example, myField: '{"foo":"bar","baz":1}'. Just don’t set the content type header to JSON.

But if you really must send a JSON request payload instead of a standard URL-encoded form data (i.e. the request body), then you can append the AJAX action to the AJAX URL – e.g. /wp-admin/admin-ajax.php?action=ajax_load_more. That way, you wouldn’t get the error 400; however, it’s up to you to retrieve/parse the JSON request payload..

Request Payload (JSON)

jQuery.ajax({
  type: 'POST',
  url: '/wp-admin/admin-ajax.php?action=ajax_load_more',
  contentType: 'application/json; charset=utf-8',
  data: {
    action: 'ajax_load_more',
    foo: 'bar',
  }
});

Request Body (Form Data)

jQuery.ajax({
  type: 'POST',
  url: '/wp-admin/admin-ajax.php',
  data: {
    action: 'ajax_load_more',
    foo: 'bar',
  }
});