Skip to main content

Custom REST Endpoints

Custom REST endpoints are useful when you need structured data for front-end components, integrations, or automations (without scraping HTML).

Security Baselines

ConcernWhat to do
AuthenticationRequire auth for private data
AuthorizationCheck capabilities (current_user_can)
ValidationSanitize parameters and enforce types
OutputReturn only what the client needs
caution

Never expose admin-only data or secrets via a public endpoint. Treat REST output like a public API unless you explicitly enforce authentication.

Endpoint Design Guidelines

GuidelineWhy
Use a namespace + version (gp/v1)Prevents collisions and enables changes later
Keep responses smallImproves performance and reduces leakage
Validate and sanitize inputsPrevents abuse and unexpected queries
Return proper errorsEasier debugging for clients
Cache when data is expensiveAvoids repeated heavy queries

Parameter Validation (Example)

Use args to validate query parameters.

Endpoint: validated parameter with sanitize callback
<?php
add_action( 'rest_api_init', function () {
register_rest_route(
'gp/v1',
'/posts',
[
'methods' => 'GET',
'permission_callback' => '__return_true',
'args' => [
'per_page' => [
'type' => 'integer',
'default' => 5,
'sanitize_callback' => 'absint',
],
],
'callback' => function ( WP_REST_Request $req ) {
$per_page = max( 1, min( 20, (int) $req->get_param( 'per_page' ) ) );
$q = new WP_Query(
[
'post_type' => 'post',
'posts_per_page' => $per_page,
'no_found_rows' => true,
]
);

$data = array_map(
static function ( WP_Post $p ) {
return [
'id' => $p->ID,
'title' => get_the_title( $p ),
'url' => get_permalink( $p ),
];
},
$q->posts
);

return rest_ensure_response( $data );
},
]
);
} );

Example: A "Site Info" Endpoint

This endpoint returns safe, public information.

Site plugin: register a simple REST endpoint
<?php
add_action( 'rest_api_init', function () {
register_rest_route(
'gp/v1',
'/site-info',
[
'methods' => 'GET',
'permission_callback' => '__return_true',
'callback' => function () {
return rest_ensure_response(
[
'name' => get_bloginfo( 'name' ),
'url' => home_url( '/' ),
]
);
},
]
);
} );

Test it:

curl: call the endpoint
curl -sL "https://example.com/wp-json/gp/v1/site-info" | jq .

Caching Expensive Responses

If an endpoint performs expensive work (heavy queries, remote calls), cache the response.

Endpoint: cache response with a transient (example)
<?php
add_action( 'rest_api_init', function () {
register_rest_route(
'gp/v1',
'/cached-site-info',
[
'methods' => 'GET',
'permission_callback' => '__return_true',
'callback' => function () {
$key = 'gp_site_info_v1';
$data = get_transient( $key );
if ( false === $data ) {
$data = [
'name' => get_bloginfo( 'name' ),
'url' => home_url( '/' ),
'time' => time(),
];
set_transient( $key, $data, 60 );
}
return rest_ensure_response( $data );
},
]
);
} );

Example: Authenticated Endpoint

If you want private data, enforce permissions.

Endpoint: require a capability
<?php
add_action( 'rest_api_init', function () {
register_rest_route(
'gp/v1',
'/admin-check',
[
'methods' => 'GET',
'permission_callback' => function () {
return current_user_can( 'manage_options' );
},
'callback' => function () {
return rest_ensure_response( [ 'ok' => true ] );
},
]
);
} );
tip

Put REST endpoints in a site plugin, not in a theme. APIs are site features and should survive theme changes.

Troubleshooting

SymptomLikely causeFix
404 from routeHook not runningEnsure code loads; check rest_api_init
401/403Permission callback deniesVerify capability/auth method
Wrong data shapeNo validationSanitize and type-check inputs
Endpoint is slowHeavy query or remote callAdd caching; reduce response size

Hands-On: Build and Test a Posts Endpoint

  1. Add the validated /gp/v1/posts endpoint.
  2. Call it with a few values.
  3. Confirm it never returns more than 20 items.
Test per_page clamping
curl -sL "https://example.com/wp-json/gp/v1/posts?per_page=3" | jq length
curl -sL "https://example.com/wp-json/gp/v1/posts?per_page=999" | jq length

Quick Reference

  • Public endpoint: safe, minimal data only
  • Private endpoint: enforce capabilities
  • Keep API code in a plugin

What's Next