Register a custom route on the rest_api_init hook with register_rest_route(). Give it a unique versioned namespace such as myplugin/v1, a URL pattern, an HTTP method, a callback, and a permission_callback. Define every parameter’s type, validation, and sanitization, then return data or a structured WP_Error. For a growing API, move the code into a WP_REST_Controller and describe the contract with JSON Schema.
Antihost offers managed WordPress hosting with free migration, one-click staging and a 14-day free trial.
This guide builds a working endpoint, explains authorization and input handling, and shows when a small function should become a controller-based API.
As an Amazon Associate I earn from qualifying purchases.
What a WordPress REST route actually contains
A REST route is a URL pattern, while an endpoint is the behavior attached to that pattern for a particular HTTP method. One route can therefore have separate read, create, update, and delete endpoints. WordPress exposes route registration through the REST API extension guide and the register_rest_route() reference.
A complete endpoint definition answers six questions:
#1 Best Overall
- Namespace: Who owns the route and which contract version is it?
- Path: Which URL pattern identifies the resource?
- Method: Is the operation read-only or does it change data?
- Callback: What code performs the operation?
- Permission callback: Who may invoke it?
- Arguments: Which parameters are accepted, and how are they typed, validated, and sanitized?
A request such as GET /wp-json/myplugin/v1/books/42 combines the standard REST prefix, your namespace and version, and a route containing the book ID.
Plan the contract before writing PHP
Choose a namespace and version
Use a namespace that is unique to your plugin or project and include a version: vendor/v1. Keep the version stable for compatible changes; introduce v2 when you must make a breaking change, such as removing a field or changing its type. A namespace also reduces collisions with WordPress core and other plugins.
Do these 3 things before closing this tab:
1Scan for outdated or missing drivers - takes under a minute2Repair Windows errors before they cause bigger problems3Fix the driver behind crashes, sound loss and screen glitchesChoose a resource-shaped path
Use nouns for resources and put identifiers in the path. For example, /books can represent a collection and /books/(?P<id>d+) a single numeric book. The regular expression restricts the path before your callback runs. Query parameters such as ?page=2 are better for filtering or pagination than for identifying the resource itself.
Decide whether the operation is public
A public route is an explicit product decision, not a shortcut. It can expose data or trigger an action for anyone who can reach the URL. Protected routes should require authentication and check the capability that represents the requested operation on the requested resource.
Build a minimal read-only endpoint
Put route registration in a plugin or a namespaced project file that loads on every request. The registration itself belongs on rest_api_init; calling register_rest_route() earlier is outside the documented lifecycle. The following example reads a post of the custom book type by ID and requires the current user to have the read capability.
<?php
add_action( 'rest_api_init', function () {
register_rest_route( 'myplugin/v1', '/books/(?P<id>\d+)', array(
'methods' => WP_REST_Server::READABLE,
'callback' => 'myplugin_get_book',
'permission_callback' => function () {
return current_user_can( 'read' );
},
'args' => array(
'id' => array(
'required' => true,
'sanitize_callback' => 'absint',
'validate_callback' => function ( $value ) {
return is_numeric( $value ) && (int) $value > 0;
},
),
),
) );
} );
function myplugin_get_book( WP_REST_Request $request ) {
$id = (int) $request['id'];
$book = get_post( $id );
if ( ! $book ) {
return new WP_Error(
'book_not_found',
'Book not found.',
array( 'status' => 404 )
);
}
return array(
'id' => $book->ID,
'title' => get_the_title( $book ),
);
}
WordPress converts the returned array to a JSON response. The callback receives a WP_REST_Request, so route variables, query values, headers, and body data are available through the request object. Returning a WP_Error gives clients a machine-readable code, a human-readable message, and the HTTP status supplied in the error data.
The Tool Desk
Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →Rank #2
Register the route step by step
- Create the namespace. Replace
mypluginwith a unique vendor or plugin identifier and retain/v1as the initial contract version. - Attach registration to the lifecycle hook. Use
add_action( 'rest_api_init', ... ); do not register the route at file load time. - Define the path. Use a collection path such as
/booksor a constrained identifier path such as/books/(?P<id>d+). - Select the method.
WP_REST_Server::READABLEis suitable for a read operation. Use the corresponding REST server method constant for creating, editing, or deleting data. - Write one focused callback. Read the request, load or change one resource, and return the representation, a
WP_REST_Response, or aWP_Error. - Add a permission callback. Every endpoint needs one. Use a capability check for protected work, or deliberately use
__return_trueonly when the endpoint is intended to be public. - Declare arguments. Mark required values, supply defaults where appropriate, and attach type, validation, and sanitization rules.
- Document the contract. Record methods, URL patterns, authentication expectations, parameters, success fields, and error codes for client developers.
The official guidance on methods, callbacks, permissions, and arguments is in Routes and Endpoints.
Validate and sanitize every input
Route variables, query parameters, and request-body values are untrusted. Validation answers “is this acceptable?” and should reject malformed input. Sanitization answers “how should an acceptable value be normalized?” For an integer ID, the example validates a positive numeric value and sanitizes it with absint. Do not use sanitization as a substitute for validation: converting an invalid value into zero can hide a bad request.
Useful argument metadata
| Property | Purpose | Example |
|---|---|---|
required |
Reject a request when the client omits the value. | true |
default |
Supply a value when omission is valid. | 1 |
type |
Describe the expected JSON value type. | string, integer, boolean, array |
enum |
Restrict a value to a known set. | array( 'draft', 'published' ) |
validate_callback |
Return true only when the value meets your rule. | Positive integer or permitted status. |
sanitize_callback |
Normalize an accepted value before business logic uses it. | absint for an ID. |
Keep validation close to the route definition so the API contract is visible. Check relationships and permissions in the callback as well—for example, an ID may be syntactically valid but refer to a resource the current user cannot access.
Read JSON request bodies correctly
For create and update operations, clients should send a JSON content type and a JSON body. In the callback, read the parsed associative array with $request->get_json_params(). Validate required fields, reject unknown or malformed values when your contract requires that, authorize the operation, then persist the change.
Quick wins for a faster PC:
Repair Windows errors before they cause bigger problemsFix Now →Scan for outdated or missing drivers - takes under a minuteDriver Scan →function myplugin_create_book( WP_REST_Request $request ) {
$params = $request->get_json_params();
if ( ! is_array( $params ) || empty( $params['title'] ) ) {
return new WP_Error(
'missing_title',
'A title is required.',
array( 'status' => 400 )
);
}
// Validate, sanitize, authorize, and save the book here.
}
Do not trust a field merely because it arrived in JSON. Normalize text for the storage API you use, constrain numeric and enum fields, and make sure the authenticated user can perform the requested write.
Authorization: login is not enough
permission_callback is authorization, not merely an authentication check. A logged-in user may still lack permission to edit a particular resource. Match the capability to the operation: reading private data, creating a record, editing someone else’s record, and deleting a record can all require different decisions.
Rank #3
Protected endpoint
'permission_callback' => function ( WP_REST_Request $request ) {
$id = (int) $request['id'];
return current_user_can( 'edit_post', $id );
},
For a collection endpoint, check the capability that governs the collection operation. For object-level access, load the target and check its capability. Return false or a WP_Error when access is denied; WordPress will produce the appropriate REST error response.
Deliberately public endpoint
WordPress documents __return_true for an endpoint intended to be public:
Free tools Windows power users keep installed
One-click scans. No signup required.
'permission_callback' => '__return_true',
Use this only after deciding that the data or action may be reached without authentication. Public does not mean “safe by default”; rate limiting, response minimization, and careful input rules remain your responsibility.
Return useful responses and errors
A successful callback can return an array, a WP_REST_Response, or another supported value. Return the resource representation clients need rather than an internal database row. For writes, returning the created or changed resource lets a client update its local state without making an immediate second request.
When an operation cannot complete, return WP_Error with:
- a stable machine-readable error code such as
book_not_found; - a clear message suitable for logs and developers; and
- an HTTP status in the error data, such as
400for invalid input,401for missing authentication,403for insufficient permission, or404for a missing resource.
Keep error codes stable across a version so clients can branch on the code rather than scrape prose. Avoid leaking credentials, stack traces, SQL, or private fields in either success or error responses.
Crashes, No Sound, or Screen Glitches?
Random freezes, missing sound and display glitches usually trace back to one bad driver. Find and replace yours safely.Free scan · under a minutePC Slower Than It Used to Be?
A free scan shows the junk files, broken settings and background clutter dragging Windows down - then fixes them in one click.Free scan · Windows 10 & 11Rank #4
Choose an implementation style
| Choice | Best fit | Trade-off |
|---|---|---|
| Small function route | One simple, usually read-only endpoint. | Fast to write, but shared behavior can become duplicated as the API grows. |
WP_REST_Controller |
Several methods on one resource, shared permissions, or a growing API. | More structure to implement, with clearer separation of registration and behavior. |
| Callback validation | A few parameters with straightforward rules. | Easy locally, but contracts are less reusable across endpoints. |
| JSON Schema | Multiple endpoints, client generation, or a machine-readable contract. | Requires designing and maintaining the schema. |
When several routes share behavior, subclass WP_REST_Controller, implement route registration and permission methods, and derive endpoint arguments from a schema. The official Controller Classes guide explains that organization. The Schema guide describes how a schema structures data so applications can reason about their interactions with the API.
Signs that it is time for a controller
- The same resource needs collection and item routes.
- Read, create, update, and delete methods share permission logic.
- Several responses expose the same fields and constraints.
- Client teams need discoverable, reusable schema information.
- Route registration, queries, and response formatting have become difficult to test in one function.
Test the route like a client
- Confirm registration. Visit the route under the site’s REST base URL, such as
/wp-json/myplugin/v1/books/42, using the method you registered. - Test an allowed request. Verify the status, JSON shape, data types, and headers expected by the client.
- Test authentication states. Try an anonymous request, an authenticated user without the capability, and an authorized user. The outcomes should match your policy.
- Test malformed input. Use a non-numeric ID, omit required fields, send an invalid enum, and provide an incorrect JSON type. Confirm that each is rejected rather than silently coerced.
- Test missing resources. Request a valid-looking ID that does not exist and check for your stable 404 error code.
- Test writes safely. Use a non-production environment, verify that only the intended record changes, and confirm the response represents the saved resource.
- Test compatibility. Keep clients on the versioned namespace and make additive changes there; reserve a new version for breaking changes.
Use an HTTP client that lets you select the method, send JSON, and inspect status codes. For authenticated routes, use the authentication mechanism already configured for your WordPress installation; never place real credentials in source code or logs.
Troubleshoot common failures
“The route is not found”
- Check the exact namespace, version, and leading slash in the route pattern.
- Ensure registration runs on
rest_api_initand that the plugin containing it is active. - Confirm that the request uses the registered HTTP method; a route can exist for
GETbut notPOST. - Check the site’s REST base URL and any server or security layer that blocks
/wp-json/.
WordPress reports a missing permission callback
Add permission_callback to every endpoint. For a protected operation, return a capability decision; for a consciously public endpoint, use __return_true. Do not suppress the warning by leaving authorization undefined.
Valid-looking IDs still produce the wrong record
Constrain the route, validate the value, sanitize it before use, and verify that the loaded object is the expected post type or resource. A positive integer alone does not prove that the record belongs to your API.
JSON fields are empty
Send the request with a JSON content type and read the body using get_json_params(). Log the received keys in a safe development environment, then validate the shape before accessing nested values.
Clients receive inconsistent errors
Centralize error codes and statuses, return WP_Error for expected failures, and keep success responses separate from error paths. A controller and shared schema become useful when multiple endpoints need the same contract.
Best Value
Document and maintain the public contract
Documentation should state the full URL, accepted methods, authentication and capability expectations, path and query parameters, JSON body fields, success response shape, error codes, and versioning policy. Explain which fields are nullable, which are read-only, and whether omitted values receive defaults.
Keep route behavior focused: a read callback retrieves the resource; a write callback validates input, authorizes the action, saves the change, and returns the changed representation. Use namespaced functions or controller methods to avoid PHP global-name collisions. When a change would break an existing client, publish it under a new namespace version rather than silently changing the old response.
Practical decision checklist
- Is the namespace unique and versioned?
- Is registration attached to
rest_api_init? - Does every endpoint declare a method, callback, and
permission_callback? - Are route, query, and body values validated before business logic uses them?
- Are values sanitized only when normalization is intended?
- Does authorization match the operation and the specific resource?
- Are success responses minimal, useful, and stable?
- Do failures return a code, message, and HTTP status?
- Are JSON bodies parsed with
get_json_params()and checked for the expected shape? - Would a controller and schema reduce duplication for the next endpoint?
- Have anonymous, unauthorized, malformed, missing-resource, and successful requests all been tested?
- Is the method, payload, response, and version documented for client developers?
Frequently Asked Questions
Where should register_rest_route() be called?
Call it from a function attached to the rest_api_init hook, normally in your plugin or project code.
Can I make a custom endpoint public?
Yes. Set its permission_callback to __return_true only when unauthenticated access is an intentional product decision; otherwise enforce the capability required by the operation.
What is the difference between validation and sanitization?
Validation rejects values that do not meet the contract. Sanitization normalizes an accepted value before processing. Use both where appropriate, and do not let sanitization hide malformed input.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.




