WordPress has two APIs to implement Ajax: Ajax API
and REST API
. Let's see how to use them.
The Ajax API
was the only option for many years. It consists of sending a POST request to /wp-admin/admin-ajax.php
. The "admin" in the URL is misleading as it can be used either in the admin or in the front-end.
The REST API
is more recent. WordPress released this API on version 4.4, and it can be considered a more modern approach for Ajax requests. It also allows for custom endpoints.
Some people consider the REST API more complex and hard to understand than the Ajax API, but it is up to you to choose one over the other. In the end, they both do the same things.
To implement an Ajax endpoint, use wp_ajax_*
(for authenticated access) or wp_ajax_nopriv_*
(for unauthenticated access):
/** Ajax route. */
add_action( 'wp_ajax_my_action', 'my_action_callback' );
add_action( 'wp_ajax_nopriv_my_action', 'my_action_callback' );
/** Ajax callback. */
function my_action_callback() {
/** Dump $_REQUEST as JSON. */
echo json_encode( $_REQUEST );
wp_die();
}
Go to the blog main page (ex. http://localhost:3000/
), open the browser console (Ctrl+F12) and execute this:
/** Ajax call. */
fetch('/wp-admin/admin-ajax.php', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: 'action=my_action'
})
.then( response => response.json() )
.then( json => console.log(json) )
.catch( error => console.error( 'error:', error ) );
It will return the dump of the arguments we sent:
Object { action: "my_action" }
Done!
In the PHP code, notice that we assigned the same callback for both authenticated and unauthenticated access. This guarantees that we will always answer to the given action.
If you want to use the same callback for both states, use a is_user_logged_in()
in the callback to differentiate these states. Alternatively, you can set two callbacks, one for each state.
If your endpoint is for authenticated users only and you want to ignore unauthenticated users, you can only set the "wp_ajax_*" action.
wp_ajax_nopriv
with no permission checking).is_admin()
or current_user_can()
) for authenticated actions.With few exceptions, you should ALWAYS sanitize the inputs before using them:
// my-plugin/my-plugin.php
$action = sanitize_text_field($_REQUEST['action']);
The sanitize_*
functions list:
sanitize_email()
sanitize_file_name()
sanitize_html_class()
sanitize_key()
sanitize_meta()
sanitize_mime_type()
sanitize_option()
sanitize_sql_orderby()
sanitize_text_field()
sanitize_textarea_field()
sanitize_title()
sanitize_title_for_query()
sanitize_title_with_dashes()
sanitize_user()
You can also use PHP's core helpers like intval( $value )
or create your own custom satinization method.
Nonces prevent hackers from tricking you into clicking on a link that does a destructive action. If you are logged in as an admin user, a permission checking won't help you if you are tricked to click on a email with a link that deletes all your blog posts.
There are two methods to work with nonces: wp_create_nonce()
and check_ajax_referer( $nonce )
. The first method creates a random string to be sent by the Ajax call. The second method verifies if the hash is valid.
Let's create this AJAX endpoint, using the REST API: GET /my-namespace/v1/something
:
function my_method_callback( $request ) {
/** The response is automatically converted to JSON. */
return $request->get_params();
}
/** Routes should be created in the "rest_api_init" action. */
add_action( 'rest_api_init', function() {
register_rest_route( 'my-namespace/v1', '/something', [
'methods' => 'GET',
'callback' => 'my_method_callback',
] );
} );
Now open it in your browser:
http://localhost:3000/index.php?rest_route=/my-namespace/v1/something
(change "localhost:3000" to your actual host)
Done!
The format is: {namespace}/{version}/{resource}
. For example, the core WP "users" endpoint is: wp/v2/users
.
WARNING
NEVER add endpoints to the wp namespace. Ex. wp/1/my-endpoint
. The wp
namespace is reserved for the WordPress core.
If your website has pretty permalinks support, then you can call the route directly. Just add a /wp-json
prefix:
http://localhost:3000/wp-json/my-namespace/v1/my-method
Use rest_url( $path )
whenever you need to print the URL. This method detects if the installation supports pretty URLs and writes the correct URL:
$url = rest_url( 'my-namespace/v1/something' );
The $url
will be:
http://localhost:3000/index.php?rest_route=/my-namespace/v1/something
http://localhost:3000/wp-json/my-namespace/v1/something
RESTful means to have a resource (ex. "/users") and allow CRUD operations (create, read, update, delete) on it.
The URL is the same for all CRUD operations. Which operation will be executed depends on the HTTP verb (GET, POST, etc):
GET /users
POST /users
GET /users/:id
PATCH /users/:id
or PUT /users/:id
or POST /users/:id
DELETE /users/:id
TIP
POST or PUT? There's an intense debate of which verb should be used to create an item. Some prefer POST, some prefer PUT. WordPress constants imply that you should use POST to create items, so that's my preference.
Let's say you want to add a POST operation to your "/something":
register_rest_route(
'my-namespace/v1',
'/something',
[
[
'methods' => 'GET',
'callback' => [ $this, 'my_method_callback' ],
],
[
'methods' => 'POST',
'callback' => [ $this, 'my_method2_callback' ],
],
]
);
In other words, pass your methods in an array.
Instead of POST/GET/etc, use the API constants:
register_rest_route( 'my-namespace/v1', '/something', [
'methods' => WP_REST_Server::READABLE, // "READABLE" == "GET"
'callback' => [ $this, 'my_method_callback' ]
] );
List of constants:
READABLE
(GET)CREATABLE
(POST)EDITABLE
(PUT, PATCH)DELETABLE
(DELETE)Alternatively, you can return a WP_REST_Response
object. This allows you to customize the headers and other things in the response:
/** HTTP code 200 means "Success". */
return new WP_REST_Response( [ 'some', 'result' ], 200 );
/** HTTP code 404 means "Not found". */
return new WP_Error( 'my_err_code', 'My err message', [ 'status' => 404 ] );
Or, with a different syntax:
$error = new WP_Error();
$error->add( 'my_err_code', 'My err message', [ 'status' => 404 ] );
return $error;
Use permission_callback
to validate a request:
register_rest_route( 'my-namespace/v1', '/something', [
'methods' => WP_REST_Server::READABLE, // "READABLE" == "GET"
'callback' => [ $this, 'my_method_callback' ],
'permission_callback' => function ( $request ) {
return current_user_can( 'edit_posts' );
}
] );
For an id, use the regex (?P<id>\d+)
:
register_rest_route( 'my-namespace/v1', '/something/(?P<id>\d+)', [
'methods' => 'GET',
'callback' => [ $this, 'my_method_callback' ]
] );
Adapt the <id>
to whatever your parameter is called. Also, change the \d+
if you accept non-numeric values.
Use args
to configure your arguments settings. You don't need to define them all, just the ones you need custom settings (validation, sanitization, etc):
'args' => [
'id' => [
/** Return true or false. */
'validate_callback' => function( $value, $request, $param ) {
return $value >= 1900;
},
/** Sanitization allows you to clean the parameters. It happens AFTER validation. */
'sanitize_callback' => function( $value, $request, $param ) {
return intval( $value );
}
],
]
You can use the sanitize_*()
helpers, PHP's core helpers (ex. intval()
) or any other methods you like.
A simpler solution is to validate and sanitize the inputs in your callback, not in args
:
function my_method_callback( $request ) {
$parameters = $request->get_params();
$username = sanitize_text_field($parameters['username']);
/** ... */
}
TIP
I prefer the first method (sanitization in args) because that's how WP wants us to do it. But feel free to decide which one you prefer the most.
I recommend using Nonces to protect your REST API, especially for the GET methods that do destructive actions. Although it's a bad practice to implement destructive actions on the GET verb ("destructive actions" means any action that changes the state, like creating a new user).
There are two methods to work with nonces: wp_create_nonce()
and check_ajax_referer( $nonce )
. The first method creates a random string to be sent by the Ajax call. The second method verifies if the hash is valid.
WP_REST_Controller
Starting on WordPress version 4.7, you can define your REST API by creating a class inherited from the WP_REST_Controller
class. Then, implement a register_routes()
public method that will define your routes:
class MyRestRouter extends WP_REST_Controller {
public function register_routes() {
register_rest_route( 'my-namespace/v1', '/something', [
'methods' => WP_REST_Server::READABLE, // "READABLE" == "GET"
'callback' => [ $this, 'my_method_callback' ]
] );
}
}
TIP
Notice that you don't need the rest_api_init
action.