Building a WordPress REST API plugin is the fastest way to turn a WordPress site into something more than a content management system. Whether you need to feed data to a mobile app, connect a headless frontend, or integrate with external services like CRMs and AI platforms, the REST API is how modern WordPress development gets done.
This guide walks through the full process of wordpress rest api plugin development – from registering custom endpoints to handling authentication, validation, and error responses. Every code example is production-ready. If you are a developer looking to build API-driven plugins or an agency evaluating what is possible, this covers the technical ground you need.
Why the WordPress REST API Matters
WordPress powers over 40% of the web. But the traditional approach – PHP templates rendering HTML on the server – limits what you can build. The REST API removes that limitation entirely.
With the REST API, WordPress becomes a structured data backend. Your site exposes JSON endpoints that any application can consume. The content stays in WordPress. The presentation layer can be anything.
This unlocks three major patterns:
- Headless WordPress: use React, Next.js, or Vue for the frontend while WordPress handles content and data
- Mobile applications: native iOS and Android apps that read and write to your WordPress database
- Third-party integrations: connect WordPress to CRMs, payment processors, analytics platforms, and AI services without building custom middleware
For wordpress api development, this is not theoretical. It is how production systems work today. Every time an agency client asks for a dashboard that pulls data from their WordPress site, or a mobile app that syncs with their WooCommerce store, the REST API is the answer.
What Comes Built In
WordPress ships with a complete set of default REST API endpoints out of the box:
/wp-json/wp/v2/posts– create, read, update, delete posts/wp-json/wp/v2/pages– same for pages/wp-json/wp/v2/users– user management/wp-json/wp/v2/media– media library access/wp-json/wp/v2/comments– comment operations/wp-json/wc/v3/*– WooCommerce endpoints (if WooCommerce is active)
These defaults handle standard content operations. But the real power comes when you register your own.
Registering Custom REST API Endpoints
The core of any wordpress rest api plugin is register_rest_route(). This function tells WordPress to listen for requests at a specific URL pattern and hand them off to your callback function.
Here is a complete example that registers a custom endpoint:
<?php
/**
* Plugin Name: Custom API Endpoints
* Description: Registers custom REST API endpoints for the application.
* Version: 1.0.0
*/
add_action( 'rest_api_init', 'cae_register_routes' );
function cae_register_routes() {
register_rest_route( 'custom-api/v1', '/leads', array(
'methods' => WP_REST_Server::READABLE,
'callback' => 'cae_get_leads',
'permission_callback' => 'cae_check_admin_permission',
) );
register_rest_route( 'custom-api/v1', '/leads', array(
'methods' => WP_REST_Server::CREATABLE,
'callback' => 'cae_create_lead',
'permission_callback' => 'cae_check_admin_permission',
'args' => cae_get_lead_schema(),
) );
}
Key details:
- Namespace (
custom-api/v1) – keeps your routes separate from core and other plugins. Always include a version number. - Methods: use the
WP_REST_Serverconstants:READABLE(GET),CREATABLE(POST),EDITABLE(PUT/PATCH),DELETABLE(DELETE). - Callback: the function that processes the request and returns data.
- Permission callback: runs before your callback. If it returns
false, WordPress sends a 403 response automatically.
Route Parameters
You can define dynamic URL segments using regex patterns:
register_rest_route( 'custom-api/v1', '/leads/(?P<id>d+)', array(
'methods' => WP_REST_Server::READABLE,
'callback' => 'cae_get_single_lead',
'permission_callback' => 'cae_check_admin_permission',
'args' => array(
'id' => array(
'required' => true,
'validate_callback' => function( $param ) {
return is_numeric( $param ) && $param > 0;
},
'sanitize_callback' => 'absint',
),
),
) );
The (?P<id>d+) pattern captures a numeric ID from the URL. WordPress passes it to your callback through the $request object.
Authentication and Permissions
Every wordpress rest api custom endpoint needs a permission callback. Skipping this – or returning true unconditionally – is a security hole.
Permission Callback Patterns
Admin-only access:
function cae_check_admin_permission( $request ) {
return current_user_can( 'manage_options' );
}
Authenticated users only:
function cae_check_authenticated( $request ) {
return is_user_logged_in();
}
Public read, authenticated write:
// For GET requests - public
register_rest_route( 'custom-api/v1', '/catalogue', array(
'methods' => WP_REST_Server::READABLE,
'callback' => 'cae_get_catalogue',
'permission_callback' => '__return_true',
) );
// For POST requests - authenticated
register_rest_route( 'custom-api/v1', '/catalogue', array(
'methods' => WP_REST_Server::CREATABLE,
'callback' => 'cae_create_catalogue_item',
'permission_callback' => function() {
return current_user_can( 'edit_posts' );
},
) );
Authentication Methods
WordPress supports several authentication approaches for REST API requests:
| Method | Best For | Security Level |
|---|---|---|
| Cookie + Nonce | Same-domain JS (wp-admin, frontend scripts) | High (CSRF protected) |
| Application Passwords | External apps, mobile clients, server-to-server | High |
| OAuth 2.0 | Third-party integrations with user consent | Highest |
| JWT (via plugin) | Single-page apps, headless frontends | High |
Application Passwords (built into WordPress since 5.6) are the simplest option for external integrations. Each user can generate multiple passwords with different labels, and they can be revoked individually without changing the main password.
Data Validation and Sanitisation
Never trust incoming data. The REST API provides built-in mechanisms for validation and sanitisation that run before your callback ever executes.
Schema-Based Validation
Define your expected data structure using JSON Schema:
function cae_get_lead_schema() {
return array(
'email' => array(
'required' => true,
'type' => 'string',
'format' => 'email',
'sanitize_callback' => 'sanitize_email',
'description' => 'Contact email address.',
),
'name' => array(
'required' => true,
'type' => 'string',
'sanitize_callback' => 'sanitize_text_field',
'description' => 'Full name of the lead.',
),
'source' => array(
'required' => false,
'type' => 'string',
'default' => 'website',
'enum' => array( 'website', 'referral', 'social', 'paid' ),
'sanitize_callback' => 'sanitize_text_field',
'description' => 'Lead acquisition source.',
),
'notes' => array(
'required' => false,
'type' => 'string',
'sanitize_callback' => 'sanitize_textarea_field',
'description' => 'Additional notes about the lead.',
),
);
}
With this schema in place, WordPress automatically rejects requests with missing required fields, wrong data types, or values not in the enum list. Your callback only receives clean, validated data.
The Callback Function
Here is a complete callback that creates a lead and returns a proper REST response:
function cae_create_lead( WP_REST_Request $request ) {
$lead_data = array(
'post_title' => $request->get_param( 'name' ),
'post_type' => 'lead',
'post_status' => 'publish',
'meta_input' => array(
'_lead_email' => $request->get_param( 'email' ),
'_lead_source' => $request->get_param( 'source' ),
'_lead_notes' => $request->get_param( 'notes' ),
),
);
$lead_id = wp_insert_post( $lead_data, true );
if ( is_wp_error( $lead_id ) ) {
return new WP_REST_Response( array(
'code' => 'create_failed',
'message' => $lead_id->get_error_message(),
), 500 );
}
$response_data = array(
'id' => $lead_id,
'name' => $request->get_param( 'name' ),
'email' => $request->get_param( 'email' ),
'source' => $request->get_param( 'source' ),
);
return new WP_REST_Response( $response_data, 201 );
}
Key practices:
- Use
WP_REST_Requesttype hint for the request parameter - Return
WP_REST_Responseobjects with appropriate HTTP status codes (201 for created, 200 for success, 4xx/5xx for errors) - Use
wp_insert_postwithtrueas the second parameter to getWP_Errorobjects on failure - Never echo output – always return response objects
Structuring a Production Plugin
A real wordpress rest api plugin needs more structure than a single file. Here is a recommended layout:
my-api-plugin/
my-api-plugin.php # Main plugin file, hooks, activation
includes/
class-rest-controller.php # Route registration
class-lead-controller.php # Lead-specific endpoints
class-auth-handler.php # Custom auth logic
assets/
js/
css/
readme.txt
Using a Controller Class
The WordPress REST API is designed to work with controller classes that extend WP_REST_Controller:
class Lead_REST_Controller extends WP_REST_Controller {
protected $namespace = 'custom-api/v1';
protected $rest_base = 'leads';
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' ),
'args' => $this->get_collection_params(),
),
array(
'methods' => WP_REST_Server::CREATABLE,
'callback' => array( $this, 'create_item' ),
'permission_callback' => array( $this, 'create_item_permissions_check' ),
'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ),
),
'schema' => array( $this, 'get_public_item_schema' ),
) );
}
public function get_items_permissions_check( $request ) {
return current_user_can( 'edit_posts' );
}
public function create_item_permissions_check( $request ) {
return current_user_can( 'manage_options' );
}
// ... get_items(), create_item(), get_item_schema() methods
}
This pattern gives you consistent method naming, built-in schema support, and cleaner code organisation across multiple endpoint groups.
Real-World Use Cases
The wordpress plugin rest api pattern is not academic. Here are concrete scenarios where custom endpoints solve real business problems.
CRM Integration
A lead management system needs to sync data between WordPress and external platforms. Custom endpoints handle incoming webhooks from CRMs, push new lead data to third-party services, and provide dashboards with real-time data.
This is exactly what Leadio does – it exposes REST API endpoints that connect WordPress with Cal.com for scheduling and Fathom Analytics for tracking, creating a unified lead management CRM that lives inside WordPress rather than requiring a separate SaaS platform.
AI Service Integration
Connecting WordPress to AI services – OpenAI, Anthropic, Google Gemini – requires custom endpoints that act as secure proxies. The frontend sends a request to your WordPress endpoint, which adds API keys server-side and forwards the request to the AI service. The response comes back through your endpoint, where you can log usage, cache results, and enforce rate limits.
AI2WP uses this pattern to let users generate content with AI models and publish it directly as WordPress pages. The REST API handles the entire flow – sending prompts, receiving completions, and creating posts – without exposing any API credentials to the browser.
Headless WordPress for Agencies
Agency clients increasingly want custom frontends built with React or Next.js while keeping WordPress as the content backend. Custom REST API endpoints let you shape the data exactly as the frontend needs it – combining posts, custom fields, taxonomy data, and computed values into a single response instead of forcing the frontend to make multiple requests.
For more on building custom solutions for agency clients, see my guide on custom WordPress plugin development for agencies.
Mobile App Backend
A WordPress site with WooCommerce can serve as the complete backend for a mobile shopping app. Custom endpoints handle:
- Product search with custom filtering and sorting logic
- Cart management tied to user sessions
- Order placement with payment gateway integration
- Push notification triggers on order status changes
External Dashboard and Reporting
Custom endpoints can aggregate data from WordPress, WooCommerce, and connected services into a single API response that feeds an external dashboard or reporting tool. Instead of querying multiple databases and APIs from the dashboard, you build one WordPress endpoint that does the heavy lifting.
Error Handling Best Practices
Proper error responses make your API usable. Poor error responses make debugging a nightmare.
Use WP_Error Consistently
function cae_get_single_lead( WP_REST_Request $request ) {
$lead_id = $request->get_param( 'id' );
$lead = get_post( $lead_id );
if ( ! $lead || 'lead' !== $lead->post_type ) {
return new WP_Error(
'lead_not_found',
'No lead found with the specified ID.',
array( 'status' => 404 )
);
}
if ( 'trash' === $lead->post_status ) {
return new WP_Error(
'lead_deleted',
'This lead has been deleted.',
array( 'status' => 410 )
);
}
return new WP_REST_Response( cae_format_lead( $lead ), 200 );
}
Error handling rules:
- Always return
WP_Errorfor error cases, notfalseor empty arrays - Include a machine-readable error code (e.g.,
lead_not_found) - Include a human-readable message
- Set the correct HTTP status code in the data array
- Use standard HTTP codes: 400 (bad request), 401 (unauthorised), 403 (forbidden), 404 (not found), 500 (server error)
Performance Considerations
REST API endpoints that run on every request need to be efficient.
- Use transients for caching: if your endpoint aggregates data that does not change every second, cache the result with
set_transient()and serve it until it expires - Paginate results: never return unbounded lists. Use
per_pageandpageparameters with appropriate defaults and maximums - Select only what you need: use
WP_Querywith specificfieldsandmeta_queryparameters rather than pulling entire post objects - Rate limit external calls: if your endpoint calls a third-party API, implement rate limiting to avoid hitting their limits and slowing down your response
// Paginated endpoint example
function cae_get_leads( WP_REST_Request $request ) {
$per_page = $request->get_param( 'per_page' ) ?: 10;
$page = $request->get_param( 'page' ) ?: 1;
$query = new WP_Query( array(
'post_type' => 'lead',
'posts_per_page' => min( $per_page, 100 ),
'paged' => $page,
'orderby' => 'date',
'order' => 'DESC',
) );
$leads = array_map( 'cae_format_lead', $query->posts );
$response = new WP_REST_Response( $leads, 200 );
$response->header( 'X-WP-Total', $query->found_posts );
$response->header( 'X-WP-TotalPages', $query->max_num_pages );
return $response;
}
Testing Your Endpoints
Before deploying, test your endpoints thoroughly.
Using cURL
# GET request
curl -X GET https://yoursite.com/wp-json/custom-api/v1/leads
-H "Authorization: Basic $(echo -n 'username:app_password' | base64)"
# POST request with data
curl -X POST https://yoursite.com/wp-json/custom-api/v1/leads
-H "Authorization: Basic $(echo -n 'username:app_password' | base64)"
-H "Content-Type: application/json"
-d '{"name": "Jane Smith", "email": "jane@example.com", "source": "referral"}'
Using Postman or Hoppscotch
GUI tools like Postman are useful for exploring your API interactively. Set up an environment with your base URL and authentication credentials, then build a collection of requests for each endpoint.
Automated Testing
For production plugins, write PHPUnit tests that exercise your endpoints:
class Test_Lead_Endpoint extends WP_Test_REST_Controller_Testcase {
public function test_create_lead_returns_201() {
$this->set_current_user_to_admin();
$request = new WP_REST_Request( 'POST', '/custom-api/v1/leads' );
$request->set_body_params( array(
'name' => 'Test Lead',
'email' => 'test@example.com',
) );
$response = rest_get_server()->dispatch( $request );
$this->assertEquals( 201, $response->get_status() );
}
public function test_unauthenticated_request_returns_403() {
wp_set_current_user( 0 );
$request = new WP_REST_Request( 'GET', '/custom-api/v1/leads' );
$response = rest_get_server()->dispatch( $request );
$this->assertEquals( 403, $response->get_status() );
}
}
When to Build Custom vs Use Existing Plugins
Not every project needs custom endpoints. Here is a decision framework:
Build custom when:
- You need endpoints that shape data for a specific frontend or app
- The integration requires business logic that no existing plugin handles
- Security requirements demand full control over authentication and data access
- Performance needs require optimised queries rather than generic plugin overhead
Use existing plugins when:
- Standard CRUD operations on default WordPress content types are sufficient
- A well-maintained plugin already exposes the exact endpoints you need
- Budget and timeline do not justify custom development
For a deeper look at building WordPress plugins from scratch, our how to build a WordPress plugin guide covers the fundamentals.
Need API-driven WordPress development? Whether you are building a headless frontend, connecting to external services, or need a custom plugin with REST API endpoints, devash.pro builds WordPress solutions that integrate with anything. From Leadio’s CRM integrations to AI2WP’s AI service connections – I build plugins that talk to the systems your business runs on. Get in touch to discuss your project.