React post to WordPress custom post type

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).