WordPress REST API sounds scary, but once you break it down, it’s actually very logical. This article will walk you through building a WordPress plugin with custom REST API endpoints in a simple, practical way. No shortcuts, no overengineering, no buzzwords.

By the end, you’ll clearly understand:

  • What the REST API is
  • Why you’d build custom endpoints
  • How to create a plugin that exposes its own API
  • How authentication, permissions, and responses work
  • Common mistakes developers make

This is written for someone who already knows basic WordPress plugin development but wants to step into more advanced territory without pain.

What Is the WordPress REST API (In Simple Terms)

The REST API lets WordPress talk to other apps.

That “other app” could be:

  • A mobile app
  • A React or Vue frontend
  • Another WordPress site
  • A custom dashboard
  • Even a simple JavaScript file

Instead of loading full WordPress pages, the REST API allows you to request data as JSON.

For example:

  • Get posts
  • Create users
  • Update orders
  • Fetch custom plugin data

WordPress already has built-in REST endpoints like:

/wp-json/wp/v2/posts

But when you build a serious plugin, those default endpoints are often not enough. That’s where custom REST API endpoints come in.

Why You’d Build Custom REST API Endpoints

You don’t build custom endpoints just because you can. You build them because:

  1. You want better control over data
  2. You need custom business logic
  3. You want to hide internal structures
  4. You want to improve performance
  5. You’re building headless or app-based features

Real examples:

  • A learning plugin exposing student progress
  • A WooCommerce extension returning custom pricing rules
  • A membership plugin validating access
  • A mobile app pulling user-specific data

Custom endpoints let your plugin speak its own language.

Basic REST API Concepts You Must Understand

Before writing code, let’s clear a few basics.

Endpoint

An endpoint is just a URL.

Example:

/wp-json/my-plugin/v1/data

Namespace

This avoids conflicts.

my-plugin/v1

Route

The actual path.

/data

HTTP Methods

Each endpoint responds to one or more methods:

  • GET – fetch data
  • POST – create data
  • PUT / PATCH – update data
  • DELETE – remove data

Response

Always returned as JSON.

Plugin Structure (Simple and Clean)

Let’s create a minimal plugin.

Create a folder:

my-custom-api-plugin

Inside it, create:

my-custom-api-plugin.php

Add this plugin header:

<?php
/**
 * Plugin Name: My Custom API Plugin
 * Description: Example plugin with custom REST API endpoints
 * Version: 1.0
 * Author: Your Name
 */

That’s enough to activate the plugin.

Registering a Custom REST API Endpoint

WordPress provides a hook specifically for REST API routes:

rest_api_init

Add this inside your plugin file:

add_action( 'rest_api_init', 'my_plugin_register_routes' );

function my_plugin_register_routes() {
    register_rest_route( 'my-plugin/v1', '/hello', array(
        'methods'  => 'GET',
        'callback' => 'my_plugin_hello_callback',
    ));
}

Now add the callback:

function my_plugin_hello_callback() {
    return array(
        'message' => 'Hello from my custom API'
    );
}

That’s it.

Visit:

/wp-json/my-plugin/v1/hello

You’ll get JSON output.

Understanding What Just Happened

Let’s break it down slowly.

register_rest_route()

Parameters:

  1. Namespace: my-plugin/v1
  2. Route: /hello
  3. Settings array

The callback function runs only when this endpoint is requested.

WordPress automatically:

  • Handles routing
  • Sends JSON headers
  • Converts arrays to JSON

No manual JSON encoding needed.

Using Request Data (GET Parameters)

Most real endpoints need input.

Example URL:

/wp-json/my-plugin/v1/user?id=5

Modify your route:

register_rest_route( 'my-plugin/v1', '/user', array(
    'methods'  => 'GET',
    'callback' => 'my_plugin_user_callback',
));

Callback:

function my_plugin_user_callback( $request ) {
    $user_id = $request->get_param( 'id' );

    if ( empty( $user_id ) ) {
        return array(
            'error' => 'User ID is required'
        );
    }

    $user = get_user_by( 'id', $user_id );

    if ( ! $user ) {
        return array(
            'error' => 'User not found'
        );
    }

    return array(
        'id'    => $user->ID,
        'name'  => $user->display_name,
        'email' => $user->user_email,
    );
}

Now your API is dynamic.

Handling POST Requests (Creating Data)

GET is easy. POST is where plugins become powerful.

Change method to POST:

register_rest_route( 'my-plugin/v1', '/save', array(
    'methods'  => 'POST',
    'callback' => 'my_plugin_save_callback',
));

Callback:

function my_plugin_save_callback( $request ) {
    $name = sanitize_text_field( $request->get_param( 'name' ) );

    if ( empty( $name ) ) {
        return array(
            'error' => 'Name is required'
        );
    }

    return array(
        'success' => true,
        'name'    => $name
    );
}

POST data can come from:

  • JavaScript fetch
  • Mobile app
  • External system

Permissions and Security (Very Important)

Right now, anyone can access these endpoints. That’s dangerous.

Every serious endpoint needs a permission check.

Add this to your route:

'permission_callback' => 'my_plugin_permission_check'

Full example:

register_rest_route( 'my-plugin/v1', '/secure', array(
    'methods'  => 'GET',
    'callback' => 'my_plugin_secure_callback',
    'permission_callback' => 'my_plugin_permission_check',
));

Permission function:

function my_plugin_permission_check() {
    return is_user_logged_in();
}

Now:

  • Logged-in users can access
  • Guests are blocked

You can go deeper:

  • Check roles
  • Check capabilities
  • Validate tokens

Using Capabilities (Best Practice)

Instead of checking roles, always check capabilities.

Example:

function my_plugin_permission_check() {
    return current_user_can( 'manage_options' );
}

This protects your plugin from role changes and custom roles.

Returning Proper Responses and Errors

WordPress provides special response classes.

Success response

return rest_ensure_response( array(
    'status' => 'ok'
));

Error response

return new WP_Error(
    'invalid_request',
    'Something went wrong',
    array( 'status' => 400 )
);

Why this matters:

  • Correct HTTP status codes
  • Better frontend handling
  • Cleaner debugging

Versioning Your API (Do This Early)

Never ship an API without versioning.

Bad:

my-plugin/data

Good:

my-plugin/v1/data

When you change behavior later:

my-plugin/v2/data

Old apps keep working.
New apps use new logic.

This saves you from breaking users.

Using REST API from JavaScript

Example fetch request:

fetch('/wp-json/my-plugin/v1/hello')
  .then(response => response.json())
  .then(data => {
      console.log(data.message);
  });

For authenticated requests, use nonces.

WordPress provides:

wp_create_nonce( 'wp_rest' );

Common Mistakes Developers Make

Let’s be honest. These happen a lot.

  1. No permission checks
  2. Returning raw database data
  3. No sanitization
  4. Breaking backward compatibility
  5. Using REST API for everything
  6. Overloading one endpoint with too much logic

REST endpoints should be:

  • Focused
  • Predictable
  • Secure

When NOT to Use Custom REST API Endpoints

Don’t use REST API if:

  • A normal admin page works fine
  • Data never leaves WordPress
  • You don’t need async behavior

REST API is powerful, but unnecessary complexity hurts maintenance.

Final Thoughts

Building a plugin with custom REST API endpoints is one of the most valuable WordPress skills you can have today.

It opens doors to:

  • Headless WordPress
  • Mobile apps
  • Scalable products
  • Modern UI frameworks
  • SaaS-style plugins

Once you understand the flow:
Route → Permission → Logic → Response

Everything clicks.

If you want, I can:

  • Add a real plugin example (WooCommerce, LMS, membership)
  • Show REST API testing with Postman
  • Cover authentication methods in detail
  • Turn this into a tutorial series

Just tell me what direction you want next.