rest_pre_serve_request filter) to intercept the data right before it’s sent and convert the JSON object into XML or CSV.
An API is an Application Programming Interface. REST, which stands for “Representational State Transfer,” is a set of concepts for modeling and accessing your application’s data as interrelated objects and collections.
API is a set of code that allows one system to interact (or “interface”) with another.
REST – Representational State Transfer, or REST, provides standards that web systems can use to interface with each other.
| Resource | Endpoint Example | Action |
|---|---|---|
| All Posts | /wp/v2/posts | Get a list of recent blog posts |
| Single Post | /wp/v2/posts/123 | Get details for post with ID 123 |
| All Users | /wp/v2/users | List all registered users on the site |
| Media Library | /wp/v2/media | Get a list of uploaded images |
| Search | /wp/v2/search?search=pizza | Search the site for “pizza” |
wp scaffold
wp scaffold plugin my-new-plugin: Creates a new plugin folder with all the necessary files (index.php,readme.txt, etc.).wp scaffold child-theme my-child --parent_theme=twentytwentyfive: Instantly sets up a child theme.wp scaffold post-type movie: Generates the PHP code needed to register a “Movie” custom post type.wp scaffold block my-block: Creates the files (PHP, JS, CSS) needed to build a custom Gutenberg block.
# Basic plugin generation
wp scaffold plugin my-awesome-feature --plugin_name="My Awesome Feature" --plugin_description="Does something cool." --plugin_author="Your Name"
# Basic plugin generation
wp scaffold plugin my-awesome-feature --plugin_name="My Awesome Feature" --plugin_description="Does something cool." --plugin_author="Your Name"
# Generate code for a 'Movie' post type
# you can paste anywhere
wp scaffold post-type movie --label="Movies" --textdomain=my-plugin
# Creates a controller class for a 'movies' endpoint
wp scaffold _rest-api movies --plugin=my-awesome-feature
- Routes & Endpoints – A route is a URI that can be mapped to different HTTP methods. The mapping of an individual HTTP method to a route is known as an endpoint.
- Requests – A request made in WordPress is an instance of the WP_REST_Request class, which is used to store and retrieve information for the current request.
- Responses – Responses are the data you get back from the API. The WP_REST_Response class provides a way to interact with the response data returned by endpoints.
- Schema – API Schema is a data structure of input and output data of each endpoint**.**
- Controller Classes – It manages the registration of routes & endpoints, handling requests, utilizing schema, and generating API responses.
class MY_Daily_Message_Controller extends WP_REST_Controller {
public function register_routes() {
register_rest_route( 'my-plugin/v1', '/message', array(
methods => 'GET',
callback => array( $this, 'get_item' ),
permission_callback => '__return_true', // Publicly accessible
) );
}
public function get_item( $request ) {
$messages = ["Code is poetry", "Keep it simple", "Debug with logic"];
$data = array( 'message' => $messages[array_rand($messages)] );
// Standardize the response
return new WP_REST_Response( $data, 200 );
}
}
// Initialization
add_action( 'rest_api_init', function () {
$controller = new MY_Daily_Message_Controller();
$controller->register_routes();
} );
- To get data from remote
$response = wp_remote_get( 'https://api.example.com/data' );
if ( ! is_wp_error( $response ) ) {
$body = wp_remote_retrieve_body( $response );
$data = json_decode( $body );
}
- To post data to remote
$response = wp_remote_post( 'https://api.example.com/submit', array(
'body' => array(
'name' => 'Gemini',
'email' => 'ai@example.com',
),
) );
You should use this instead of standard PHP
curlbecause it handles different server environments automatically.
Always use
is_wp_error()when working withwp_remote_*functions. If the site has no internet connection or the DNS fails, they won’t return an HTTP error code; they’ll return aWP_Errorobject that will crash your site if you try to treat it like an array.
function get_crypto_price() {
$url = 'https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=usd';
$response = wp_remote_get( $url );
// 1. Check for connection/DNS errors
if ( is_wp_error( $response ) ) {
return 'Could not reach API';
}
// 2. Check for HTTP success (200 OK)
$code = wp_remote_retrieve_response_code( $response );
if ( $code !== 200 ) {
return 'API error code: ' . $code;
}
// 3. Parse the data
$body = wp_remote_retrieve_body( $response );
$data = json_decode( $body, true );
return '$' . $data['bitcoin']['usd'];
}
function send_lead_to_external_crm( $user_name, $user_email ) {
$url = 'https://example-crm.com/api/leads';
$args = array(
'method' => 'POST',
'timeout' => 5,
'redirection' => 5,
'headers' => array(
'Authorization' => 'Bearer your-api-token',
'Content-Type' => 'application/json',
),
'body' => json_encode( array(
'full_name' => $user_name,
'email' => $user_email,
'source' => 'WordPress Site'
) ),
);
$response = wp_remote_post( $url, $args );
if ( is_wp_error( $response ) ) {
error_log( $response->get_error_message() );
return false;
}
return true;
}
| Function | Purpose |
|---|---|
rest_url() | Returns the API URL for the current blog/site. |
get_rest_url() | Used in Multisite environments to get the API URL for a specific blog ID. |
add_action( 'rest_api_init', function () {
register_rest_route( 'my-utility/v1', '/clear-cache', array(
'methods' => 'POST',
'callback' => 'my_simple_cache_callback',
'permission_callback' => function() { return current_user_can('manage_options'); },
) );
} );
class My_Books_Controller extends WP_REST_Controller {
public function __construct() {
$this->namespace = 'my-library/v1';
$this->rest_base = 'books';
}
// 1. Register the actual URLs
public function register_routes() {
register_rest_route( $this->namespace, '/' . $this->rest_base, array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_items' ),
'permission_callback' => array( $this, 'get_items_permissions_check' ),
),
array(
'methods' => WP_REST_Server::CREATABLE,
'callback' => array( $this, 'create_item' ),
'permission_callback' => array( $this, 'create_item_permissions_check' ),
),
) );
}
// 2. Permission Checks (Security first!)
public function get_items_permissions_check( $request ) {
return true; // Anyone can view books
}
public function create_item_permissions_check( $request ) {
return current_user_can( 'edit_posts' ); // Only staff can add books
}
// 3. The Logic: Get all books
public function get_items( $request ) {
$books = get_posts( array( 'post_type' => 'book' ) );
$data = array();
foreach ( $books as $book ) {
$data[] = $this->prepare_item_for_response( $book, $request );
}
return rest_ensure_response( $data );
}
// 4. Format the output
public function prepare_item_for_response( $item, $request ) {
return array(
'id' => $item->ID,
'title' => $item->post_title,
'link' => get_permalink( $item->ID ),
);
}
}
add_action( 'rest_api_init', function () {
$books_controller = new My_Books_Controller();
$books_controller->register_routes();
$persons_controller = new My_Persons_Controller();
$persons_controller->register_routes();
} );
- To restrict the response to only those properties with this fields query:
/wp/v2/posts?_fields=author,id,excerpt,title,link
?_fields=meta.one-of-many-keys
- To embed resources to prevent unncessary later calls
/wp/v2/posts?_embed=author,wp:term
will only embed the post’s author and the lists of terms associated with the post.
POST /wp-json/wp/v2/posts/42 HTTP/1.1
Host: example.com
X-HTTP-Method-Override: DELETE
Similarly to _method, some servers, clients, and proxies do not support accessing the full response data. The API supports passing an _envelope parameter, which sends all response data in the body, including headers and status code.
?page=: specify the page of results to return.- For example,
/wp/v2/posts?page=2is the second page of posts results - By retrieving
/wp/v2/posts, then/wp/v2/posts?page=2, and so on, you may access every available post through the API, one page at a time.
- For example,
?per_page=: specify the number of records to return in one request, specified as an integer from 1 to 100.- For example,
/wp/v2/posts?per_page=1will return only the first post in the collection
- For example,
?offset=: specify an arbitrary offset at which to start retrieving posts- For example,
/wp/v2/posts?offset=6will use the default number of posts per page, but start at the 6th post in the collection ?per_page=5&page=4is equivalent to?per_page=5&offset=15
- For example,
To determine how many pages of data are available, the API returns two header fields with every paginated response:
X-WP-Total: the total number of records in the collectionX-WP-TotalPages: the total number of pages encompassing all available records
?order=: control whether results are returned in ascending or descending order- Valid values are
?order=asc(for ascending order) and?order=desc(for descending order). - All native collections are returned in descending order by default.
- Valid values are
?orderby=: control the field by which the collection is sorted- The valid values for
orderbywill vary depending on the queried resource; for the/wp/v2/postscollection, the valid values are “date,” “relevance,” “id,” “include,” “title,” and “slug” - See the REST API reference for the values supported by other collections
- All collections with dated resources default to
orderby=date
- The valid values for
WP rest api uses cookie authentcation + nonces technique. using built in js api, auto handles this api for us. In manual ajax requests, nonces need to be passed.
application passwords
curl --user "USERNAME:PASSWORD" https://HOSTNAME/wp-json/wp/v2/users?context=edit
add_action( 'rest_api_init', function () {
register_rest_route( 'rt/v1', '/celebs', array(
'methods' => 'GET',
'callback' => 'get_rt_celebs_posts',
'permission_callback' => '__return_true', // Public access
'args' => array(
'page' => array( 'default' => 1, 'sanitize_callback' => 'absint' ),
'per_page' => array( 'default' => 10, 'sanitize_callback' => 'absint' ),
),
) );
} );
function get_rt_celebs_posts( $data ) {
$args = array(
'post_type' => 'rt-celebs',
'posts_per_page' => $data['per_page'],
'paged' => $data['page'],
);
$query = new WP_Query( $args );
$posts = $query->get_posts();
$response = array();
foreach ( $posts as $post ) {
$response[] = array(
'id' => $post->ID,
'title' => $post->post_title,
'link' => get_permalink( $post->ID ),
);
}
// Wrap in WP_REST_Response to include pagination headers
$result = new WP_REST_Response( $response, 200 );
$result->header( 'X-WP-Total', $query->found_posts );
$result->header( 'X-WP-TotalPages', $query->max_num_pages );
return $result;
}
- Restricting request to one hosts/url
add_action( 'rest_api_init', function () {
register_rest_route( 'rt/v1', '/celebs', array(
'methods' => 'GET',
'callback' => 'get_rt_celebs_posts',
'permission_callback' => '__return_true', // Public access
'args' => array(
'page' => array( 'default' => 1, 'sanitize_callback' => 'absint' ),
'per_page' => array( 'default' => 10, 'sanitize_callback' => 'absint' ),
),
) );
} );
function get_rt_celebs_posts( $data ) {
$args = array(
'post_type' => 'rt-celebs',
'posts_per_page' => $data['per_page'],
'paged' => $data['page'],
);
$query = new WP_Query( $args );
$posts = $query->get_posts();
$response = array();
foreach ( $posts as $post ) {
$response[] = array(
'id' => $post->ID,
'title' => $post->post_title,
'link' => get_permalink( $post->ID ),
);
}
// Wrap in WP_REST_Response to include pagination headers
$result = new WP_REST_Response( $response, 200 );
$result->header( 'X-WP-Total', $query->found_posts );
$result->header( 'X-WP-TotalPages', $query->max_num_pages );
return $result;
}
- CORS
add_filter( 'rest_pre_serve_request', function( $value ) {
header( 'Access-Control-Allow-Origin: https://your-frontend-app.com' );
header( 'Access-Control-Allow-Methods: GET, POST, OPTIONS' );
header( 'Access-Control-Allow-Credentials: true' );
return $value;
});
Execution
rest_api_loaded() checks if it is rest route, if yest, it defines constant REST_REQUEST to true
rest_Get_server() initialises the server init action rest_api_init is executed here returns object wp_rest_server
serve_requst() function called
first checks current user is logged in or not
adds header
adds cors
CREATE WP REST REQUEST OBJECT from $_SERVER(‘REQUEST_METHOD’) $_GET $_POST variables
check_authentication() function
dispatch() function
rest_pere_dispatch filter is used to check sata bedofew dispatch
match routes and call it.
Execution - response
‘rest_request_before_callbacks’ filter is excuted befoer any clalbakcs if no errors, callbacks are executed
then
‘rest_request_after_callbacks’ executed
_envelope is chcked
json data is returned
die() is called
add_action('rest_api_init', function () {
register_rest_route('my-namespace/v1', '/submit', [
'methods' => 'POST',
'callback' => 'handle_my_post_request',
'permission_callback' => '__return_true', // Ensure you add real auth here
]);
});
function handle_my_post_request(WP_REST_Request $request) {
// Get the raw body from the request
$body = $request->get_body();
// Verify if it is valid JSON
json_decode($body);
if (json_last_error() !== JSON_ERROR_NONE) {
return new WP_Error(
'rest_invalid_json',
'The body provided is not a valid JSON string.',
['status' => 405] // Your requested status code
);
}
// Continue with your logic...
return rest_ensure_response(['message' => 'Success!']);
}
- Can you change the REST base from wp-json? Yes, via the rest_url_prefix filter:
add_filter( 'rest_url_prefix', function() {
return 'api';
});
- Do internal rest calls
$request = new WP_REST_Request( 'GET', '/wp/v2/posts' );
$response = rest_do_request( $request );
How JSONP Works
Instead of making an AJAX request, JSONP:
Creates a
<script>tag dynamically.Points its
srcto the external API.Passes a callback function name in the URL.
The server wraps the JSON data inside that callback function.
The browser executes it as JavaScript.
to enable jsonp
// Warning: This opens up potential security vulnerabilities (Rosetta attacks)
add_filter( 'rest_jsonp_enabled', '__return_true' );
- Unlike requests sent over
admin-ajax.php, the REST API doesn’t load the WordPress admin section via/wp-admin/includes/admin.php, nor does it fire theadmin_initaction hook. Based on that, it would seem that any plugins or themes that don’t rely on admin-specific functionality—but are making asynchronous requests usingadmin-ajax.php—should see a slight performance boost by switching over to the REST API.