1

I am not sure if this is the right way to do it but I have a react site using WordPress API as the backend. I want to store my contact form entries in WordPress so I created a custom post type

 register_post_type('contact', array(
    'supports'      => array('title'),
    'has_archive'   => false,
    'show_in_rest'  => true,
    'public'        => true,
    'labels'        => array(
            'name'         => 'Contact DB',
            'add_new_item' => 'Add New Contact',
            'edit_item'    => 'Edit Contact', 
            'all_items'    => 'All Contacts',
    ),
    'menu_icon'     => 'dashicons-format-chat'
));

I am trying to keep it simple for now on the frontend and at least try store the title

  const onSubmit = async (data) => {
    try {
      const response = await axios.post(
        `${process.env.NEXT_PUBLIC_API_ENDPOINT}/contact`,
        {
          title: data.name,
        }
      );
    } catch (err) {
      console.log(err);
    }
  };

This should not be protected or require authorisation as It is just meant to collect contact form entries and a user doesn't need to be logged in to create records. However, I am getting a 401 error

{
    "code": "rest_cannot_create",
    "message": "Sorry, you are not allowed to create posts as this user.",
    "data": {
        "status": 401
    }
}
10
  • Creating a post via the default endpoint (at /wp/v2/contact) requires authentication by default, i.e. in the API request, you need to include a user having the permission to create posts on the site, e.g. yourself.
    – Sally CJ
    Commented Mar 26, 2021 at 22:25
  • @SallyCJ ah I see, thank you. Is it possible to create a custom route that wouldn't require authentication (not sure if this is an option) Commented Mar 27, 2021 at 10:53
  • I think that since I am using react for the frontend I probably can't access that nonce on the server like I would with jQuery though as if this was a standard Wordpress setup? Commented Mar 27, 2021 at 10:59
  • 1
    Sorry for the late reply.. and not sure if you still need help, but you could actually store the JWT token in a cache to avoid having to request the token each time you make your API requests. And yes, you can easily create custom REST API endpoints, but I think you should just use the default endpoint/route for your CPT.
    – Sally CJ
    Commented Mar 29, 2021 at 18:22
  • 1
    Also, JWT is not the only method you can use to authenticate your REST API requests, and in fact, WordPress 5.6+ has its own application password which can be used from a remote app/site. :)
    – Sally CJ
    Commented Mar 29, 2021 at 18:23

1 Answer 1

3

I am not sure if this is the right way to do it

I want to store my contact form entries in WordPress so I created a custom post type

It's not the only right way, but IMO, it is a good approach. :)

It is just meant to collect contact form entries and a user doesn't need to be logged in to create records. However, I am getting a 401 error

As I said in the comments, creating a post via the default endpoint (e.g. at /wp/v2/contact in your case) requires authentication by default, i.e. in the API request, you need to associate a user having the permission to create posts on the site, e.g. yourself.

And if you visit the above link, the default authentication method is cookie-based which requires the current user be logged-in to your WordPress site, hence that method isn't suitable in your case since the contact form is available to everyone (registered/logged-in users and those who aren't).

So you might want to consider using application passwords instead, which in the past you'd need to use a plugin like this, but with WordPress 5.6+, you no longer need to use a 3rd-party plugin. :)

But I'm not going to talk further about what authentication method should you use or how should they be used, however, I still wanted to say that if I were to authenticate my API requests via JS and the current user is not logged-in to WordPress, I think I would not use JWT because it normally requires us to send our WordPress password when requesting for a token and we certainly don't want to expose our precious password! :)

This should not be protected or require authorisation

In that case, you can disable the authentication for your CPT's default API endpoint, and here's one easy way of doing it: Use a custom rest_controller_class for your CPT.

The steps:

  1. Extend the WP_REST_Posts_Controller class and modify the create_item_permissions_check() method (which checks if a given request has access to create a post) like so:

    // File name: class-my-wp-rest-contact-controller.php
    
    class My_WP_REST_Contact_Controller extends WP_REST_Posts_Controller {
        public function create_item_permissions_check( $request ) {
            if ( ! empty( $request['id'] ) ) {
                return new WP_Error(
                    'rest_post_exists',
                    __( 'Cannot create existing post.' ),
                    array( 'status' => 400 )
                );
            }
    
            return true;
        }
    }
    
  2. Then set the rest_controller_class to the name of the above class:

    // On 'init', load the above class file:
    require_once '/path/to/class-my-wp-rest-contact-controller.php';
    
    // Then register your CPT:
    register_post_type( 'contact', array(
        // ... your other args.
    
        'show_in_rest'          => true,
        'rest_controller_class' => 'My_WP_REST_Contact_Controller',
    
        // ... your other args.
    ) );
    

That's all, but feel free to futher customize the class (and just let me know if you need any clarification).

Update

Note that the above steps would only allow unauthenticated requests to create draft posts with basic post data like title. So basically, those steps would only help in getting rid of the rest_cannot_create error (note the "create" vs "publish" below).

To create published posts (post_status = publish), the request would need to be authenticated, and if not, then you'd get an error with the code rest_cannot_publish.

But instead of having to authenticate, you can extend the handle_status_param() method in the REST controller class like so:

public function handle_status_param( $post_status, $post_type ) {
    if ( in_array( $post_status, array( 'draft', 'publish' ) ) ) {
        return $post_status;
    }

    return parent::handle_status_param( $post_status, $post_type );
}

So in the above example, I'm allowing unauthenticated requests to set the post status to draft or publish, and for other statuses, I'm letting the parent class handle it.

And note that there are other permission checks done in the (parent) REST controller class, so you'd need to check the source for the relevant code should you get another error originating from that class, and then extend the method containing that code. (Remove, bypass or modify a restriction)

Additional Notes

  • If you intend on making the "contact" posts be published, then you should exclude the post type from search results by setting the exclude_from_search argument to true (or maybe just set public to false, then set both publicly_queryable and show_ui to true).
12
  • Thanks Sally, I really like this answer whereby I can disable the need for authenticating at all as it just doesn't make sense to have to authenticate for a contact form. I will give this a go and give you some feedback. Thanks again! Commented Apr 2, 2021 at 14:22
  • Sure, take your time. 🙂
    – Sally CJ
    Commented Apr 2, 2021 at 19:32
  • So, I am not having much luck with this. I think I am putting the code for step 1 in the wrong place. I see you have a file name there but I don't actually know where in my files to put that file so I tried putting the code in my functions.php file and then I tried putting it into a custom plugin and neither worked. My custom post type register_post_type code is in a mu-plugins folder Commented Apr 5, 2021 at 8:01
  • 1
    Thanks so much for this Sally. I tested it out and your plugin works 100% but I still got the 401 error when posting from React. HOWEVER, I found the issue! I wanted it to be published by default so I had this status: "publish",. When I removed it from my post request data it worked! Seems like this was the issue the whole time! Seriously... Anyway, thanks again. Is there a way I can make it published by default? Commented Apr 9, 2021 at 13:15
  • 1
    Not wasting my time at all, I am really grateful for your help! Thanks again so much for everything :) Commented Apr 10, 2021 at 10:44

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.