We will use the WordPress REST API Routes to retrieve questions and scores used for building our Quiz in React. We need a route to retrieve the questions with their answers and to submit the answers so we can get our result. In this tutorial, we will proceed to our WordPress roadmap and define our custom post type, metabox and also the WordPress REST API Routes.

If you have not yet learned about what we are going to build and the roadmap for each part, you can check the last article: Building a Quiz with React and WordPress REST API: Introduction.

The Base Plugin

In the last article, we did not create a base plugin. So let’s do that now. Create a folder in the plugins folder and name it wp-quiz-tut. Create a new php file inside that folder with the same name wp-quiz-tut.php.

Inside that folder create another one and name it inc. We will use that folder for additional files. Create 2 empty php files: class-wpqr-metaboxes.php and class-wpqr-rest-api.php.

This file wp-quiz-tut.php  will hold the main class of our plugin that is going to be used when loading the plugin and all the dependencies. Add this code to the file.

Now activate the plugin inside the WordPress admin area.

Quiz Questions

We need only one custom post type and that is the question post type. This post type will be used for storing the information of our questions and the answers.

Let’s now create our custom post type. We will extend the load method and add a new once load_cpts.

I won’t go into details on how to configure your custom post type, but if you want to know, do check the Codex or the Developer Resources. If you refresh the admin area, you will now see a Questions menu.

Answers Metabox

For the metabox, we will create a new class WPQR_Metaboxes that can be used for defining various metaboxes. Before that we will register the registration method in our main class WPQR.

For now, your plugin would not work and it will break your site. That’s because we still have not defined our WPQR_Metaboxes class. We will do that right now.

Open the file class-wpqr-metaboxes.php that we created before and add the code below.

Inside of our class, we have the static method answers that is called as a callback function for the registered metabox. Inside of that method, we are retrieving the answers.

For the sake of simplicity, I’ve made a form of 3 static answers. You could enhance that and create a repeater field for adding and removing answers. If you want to see how to do that, you can read my article on How to create a Repeater Field with wp.template().

Inside that form, I am checking for each answer if there is a defined value for text and points. If it is, we are displaying that data. Otherwise, we are displaying an empty string for the answer’s text and 0 for points.

Saving the Answers

Since we will save those answers in one postmeta field, we need to define a save method that will do just that. But before we do that, let’s add that save method in our main class.

It is important to understand how this data is posted on our server before we create the method save.

If we add the text for each answer and points next to it and then try to save the post, we would receive something like this on the server:

We will now need to map the points to the answers based on their order (index). Let’s now define our save method.

Basically, we are going through each posted answer and then we are checking if there are any points defined on the same order (index) as the current answer. If it is, we are mapping them together. In the end, we will have an array like this:


The last part is to define the REST API Routes. We will do that in a static method register_routes in our new class WPQR_REST_API. Let’s load that in our main class:

Now our static method will be called so we need to define that. If you’re interested on how to create custom WordPress REST API routes check the handbook.

Questions Route

Open the file class-wpqr-rest-api.php and add the code below.

In the first method, we are register the route through the function register_rest_route. We are providing the “rest namespace” of our plugin which will be wpqr/v1. The endpoint is questions.

By doing that, we can load this by using yoursite.com/wp-json/wpqr/v1/questions.

In the method get_questions we are checking if have a cached version of the questions. If not, we are retrieving all the questions. After we have retrieved all the questions, we are getting all the answers and then we are also filtering the answers array.

We don’t want to include the points in the output because we could just check the network tab in our browser and see all the points associated with answers. After filtering, we are only getting the Answer’s text.

In the screenshot below, you’ll see how this response can look like. Since I have entered only one question, you can only see the one object 🙂

Result Route

Now it’s time to define our result route. On this route, we will expect an array answers inside the body of the request. If there is none, we will throw an error. If there is, we will go through each answer where the key will be the Question’s ID and the value will be the index of the answer.

By using the WP_REST_Server::CREATABLE, we are defining that only methods inside that constant will trigger our callback. If not, WordPress will send an error saying there is no such route for that method.

The method get_results is used to retrieve the user’s result. First, we are checking if we have any answers by using the method get_body_params() on the $request (this is passed to each REST API callback). If there are no answers, we will get an error response.

Here is an example of that when I don’t send anything inside the body of the request.

If there are answers, then we are going through each of them, we get the answers for that question and then we are calculating the points as described in the comments of the code.

Here is an example of it when I pick the third answer that has 10 points.

Securing the REST API

We could include a permission_callback there and define a function that will return true or false based on my criteria. We can check for the _wpnonce and if that is not posted, we don’t allow this route to execute.

REST API does check for the _wpnonce and it validates it if that is posted. If not, it assumes that we are allowing the execution of our route without it (which if fine). Let’s see what happens if I include an invalid _wpnonce in the request.

Download the Plugin

You can download the current state of our plugin here in a ZIP file.

This part is available only to the members. If you want to become a member and support my work go to this link and subscribe: Become a Member


By using the WP REST API, we can easily define how we retrieve data and also how we post the data. By having our own callback functions we can make the data much easier to parse and use in our React Apps.

Have you already defined some custom REST API routes? Are you using them with some complex Authentication? Please do share in the comments below!

Become a Sponsor

Posted by Igor Benic

Web Developer who mainly uses WordPress for projects. Working on various project through Codeable & Toptal. Author of several ebooks at https://leanpub.com/u/igorbenic.


  1. Thanks for the great article!
    Is it better to pass the return value to rest_ensure_response function?

    For your question, I am using JWT way for authentication, using the JWT for wp plugin: https://wordpress.org/plugins/jwt-authentication-for-wp-rest-api/

    What are you using for authentication?


    1. Thank you Ali, yes the rest_ensure_response can be also used to ensure the returned format is the same for every response. I was not aware of that function.

      For now, I have not tried building apps with REST API that required authentication but I do look forward to building some.


  2. Inside `class-wpqr-rest-api.php` you can use `wp_list_pluck` instead of `array_map`.



    1. Hi George, you’re correct. I have forgotten about that function 🙂


Leave a reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.