So, I have a plugin that uses Ajax to dynamically build the post content piece by piece for a custom post type. Once the user has build their page, they hit Update/Publish and Ajax sends the entire built page to a the main plugin PHP file where I am attempting to call wp_insert_post().

Here is my relevant code (and yes the var new_content is set and is correct):

    url: ajaxurl,
    method: 'POST',
    data : {
        update_or_create: '1',
        post_content: new_content,
        post_title: jQuery('#title').val(),
        pid: jQuery('#post_ID').val(),
        action : "update_or_save_zen_page"
    } , 
    success : function(data, textStatus, XMLHttpRequest) {
            console.log(data);           // the data sent to the php file is correct
            console.log(textStatus);     // reads 'success'
            console.log(XMLHttpRequest); // normal from what I can tell, "readyState: 4"
    error : function(XMLHttpRequest, textStatus, errorThrown) {
            alert("An unknown error has occurred"); // this does not fire

Here is the php code this is posting to:

function pbfcz_save_zen_page() {

    $pid = ( is_numeric($_POST['pid']) ? $_POST['pid'] : 0);
    $the_content = sanitize_post_field ('post_content', $_POST['post_content'], $pid);
    $the_title = sanitize_text_field ( $_POST['post_title'] );

    $post_array = array (
        'ID'           => $pid,
        'post_title'   => $the_title,
        'post_content' => $the_content, // at this point, $the_content is correct
        'post_status'  => 'publish',
        'post_type'    => 'zen_page'

    $insert = wp_insert_post( $post_array, true ); // this returns the ID every time, meaning successful update, but it is not always successful

    //echo $insert;      // this is always the post ID, meaning success
    echo $the_content; // this is still correc


When the user selects save, I have tracked the data all the way until the argument for wp_insert_post(). It is ALWAYS correct. The function ALWAYS returns the post ID, meaning is was successful. I don't get a WP_Error returned or any HTML code errors like 404 or 500 or anything like that.

I don't get any errors in the chrome developer tools console either. But it updates successfully SOMETIMES - maybe every 1 in 10 tries it works.

I have tried using GET instead of POST

I have tried removing the 'post_status' => 'publish' from the arguments.

I have tried converting the post_content to UTF8 by using utf8_encode().

I have tried using post_content_filtered rather than post_content.

Nothing seems to be working, and it works randomly, and I cannot find a pattern.

The only other piece of information I have that may be related is that images have a hard time loading on this same page. If there is an image in the page content, I get errors such as




sometimes on page load. The server tries to load the image for over a minute then times out (but the image is already displaying on the page for that entire minute, then when it times out the image disappears).

Can anybody suggest why this is not working (with zero errors), but does work sometimes? Thank you all in advance and let me know if you need more code or background.

  • 2
    There's a dedicated REST API endpoint that does these things built into core, you can eliminate your custom AJAX handler, and use the endpoint with minimal changes to your javascript! Seriously dont bother with AJAX endpoints
    – Tom J Nowell
    Aug 25, 2017 at 18:59

2 Answers 2


Don't bother fixing your AJAX callback, there's already a REST API endpoint that's well tested and does all of this out of the box:


So lets enqueue a helper script to give us the URL and a security token/nonce:

wp_enqueue_script( 'wp-api' );

You will must enable the REST API for your custom post type by adding this option when registering your post type. If you don't do this then the zen_page rest endpoints will not be created by core:

'show_in_rest' => true

Then adjust the JS:

var post_id = 1;
    url: wpApiSettings.root + 'wp/v2/zen_page/'+post_id,
    method: 'POST',
    beforeSend: function ( xhr ) {
        xhr.setRequestHeader( 'X-WP-Nonce', wpApiSettings.nonce );
    data : {
        content: 'post content goes here',
        title:   'test post',
        status:  'publish'
}).fail( function( response ) {
    // failure
    console.log( 'failure' );
    console.log( response );
}).done( function( response ) {
    // success!
    console.log( 'success' );
    console.log( response );

WP automatically includes the wpApiSettings object when you enqueue wp-api, and data takes these values. You'll need an active login cookie to do this. If that's not an option, you can create new rest endpoints

If you want to create a new one, send a POST request here:


If you want to update one, send a POST request here:


Where 2 is the post ID you want to update. Send a DELETE request to delete it. In a REST API, you don't pass post IDs or actions, instead you use URLs, the same way different urls load different pages, different endpoints do different things. You can GET/POST/DELETE/PUT, and these are all the standard HTTP request types, not a parameter passed in the request.

Follow Up Notes


This will let you modify what core uses to create the endpoint, e.g.

'rest_base' => 'bananas'

will change your endpoints URL to wp/v2/bananas. Note that you do not want to name this pages as there is already a pages endpoint provided by core.


This might actually be better off if you just used the page post type and used a custom taxonomy to differentiate the zen_page's from normal pages.

It doesn't work

The REST API is designed to indicate why it didn't work, e.g. if I try to visit wp-json/wp/v2/skjlfnvlkdjfv I'll get:

{"code":"rest_no_route","message":"No route was found matching the URL and request method","data":{"status":404}}

and if I try to use the wp-json/wp/v2/settings endpoint in incognito mode:

{"code":"rest_forbidden","message":"Sorry, you are not allowed to do that.","data":{"status":403}}

Admin AJAX will return 0 in all of these scenarios, so be thankful but pay attention to failure or error messages. Here we saw I tried to make a request to an endpoint that doesn't exist, then again to an endpoint I didn't have permission for as I was logged out.

  • Thanks Tom, unfortunately that did not work. I can confirm wp-api is loaded by typing wpApiSettings into the console, and I did update my post type to use "show_in_rest". I added "ID: post_id," to the data arguments. There are no errors showing, but nothing gets saved. I removed the post_ID from the URL to create a new one, but again, nothing. Aug 25, 2017 at 20:20
  • Update: I removed ID from the data arguments, and I removed the post_ID from the URL, and it does work if I need to create a brand new post. Aug 25, 2017 at 20:31
  • @JeremyMuckel you shouldn't be including the post ID and actions, my example should work as is if you followed all the steps if you're logged in at the time. See that i used content not post_content. In a REST API, where you POST to is significant, hence adding the post ID on the end, they're not the same URL. One is an endpoint for zen_pages, the other with the post ID is the endpoint for that specific post, that's what determines things, not a post ID parameter in data. Did you follow all the steps?
    – Tom J Nowell
    Aug 26, 2017 at 1:40
  • I am using your exact code now, and I followed all steps yes. To create a new one, I removed the post_id from the URL. However when I hit "Publish", two copies get made of the same post. One is correct and one is empty. When I add the post_id to the URL for updating, nothing ever gets updated. Do I need to change some settings in my register_post_type call? Should I use a different "rest_base" or "rest_controller_class"? It should be noting when including "rest_base => pages" the duplicate does not get created on publish, but it has no content :( Aug 28, 2017 at 14:25
  • Also, you say don't bother with AJAX endpoints, but why? It seems WP-AJAX and WP-REST-API are both viable options. Aug 28, 2017 at 15:14

Thanks to @TomJNowell, I was able to get around using wp_insert_post(), which would not work, and update my posts with the php version of the REST API.

I had to:

1) Include 'show_in_rest' => true in my custom post type's registration.

2) Enqueue the helper script: wp_enqueue_script( 'wp-api' );

3) Use this php to handle the jquery post request

add_action('wp_ajax_update_or_save_zen_page', 'pbfcz_save_zen_page', 99999);
function pbfcz_save_zen_page() {
    $pid = $_POST['pid'];
    $the_content = sanitize_post_field ('post_content', $_POST['post_content'], $pid);
    $the_title = sanitize_text_field ( $_POST['post_title'] );  
    $the_content = str_replace('\"', '"', $the_content);

    $_rest = new WP_REST_Request('PUT', '/wp/v2/zen_page/' . $pid);
    $_rest->set_param( 'title', $the_title);
    $_rest->set_param( 'status', 'publish');
    $_rest->set_param( 'content', $the_content);

    $response = rest_do_request($_rest);
    echo 'done';

    wp_die(); // do not remove

My jQuery did not change much from the original question, posting here for clarity:

        url: ajaxurl,
        method: 'POST',
        data : {
            post_content : new_content,
            post_title   : jQuery('#title').val(),
            pid          : post_id,
            action       : "update_or_save_zen_page"
        } , 
        success : function(data, textStatus, XMLHttpRequest) {
        error : function(XMLHttpRequest, textStatus, errorThrown) {
            alert("An unknown error has occurred and the ajax request to create/update the post has failed.");
  • You should choose Toms post as the answer (in my opinion) and by this reward his effort.
    – Luckyfella
    Aug 29, 2017 at 20:28
  • I was thinking the same thing. I am new here, and I am not sure if I should have posted the actual answer that I arrived at, or accept the very helpful post that did not answer the question but that led to the answer by Tom Aug 29, 2017 at 21:05

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.