Skip to Content
WordPress Plugins

WordPress REST API Custom Endpoints: 2026 Practical Guide

WordPress REST API Custom Endpoints: 2026 Practical Guide

WordPress REST API custom endpoints are how serious plugins expose data and operations to JavaScript clients, mobile apps, headless frontends, and integrations. The Settings API and admin-ajax both still work, but REST is the modern default. Built right, custom endpoints feel like first-class WordPress APIs. Built wrong, they leak data, fail validation, and confuse integrators.

This guide covers the REST API patterns I run on every plugin that exposes endpoints in 2026. Registration, JSON Schema validation, authentication patterns (cookie, application passwords, JWT, OAuth), permission callbacks, error formatting, and headless-WordPress edge cases.

Quick verdict: use register_rest_route() from a controller class, define args with JSON Schema for free validation, return WP_REST_Response with proper status codes, and write a permission_callback that genuinely checks capabilities. Skipping any of these is how plugins get CVEs.

Registering a custom REST endpoint

The basic registration pattern, in a controller class:

  • Hook into rest_api_init action
  • Call register_rest_route('my-plugin/v1', '/orders', $args)
  • $args includes: methods, callback, permission_callback, args (JSON Schema)
  • Use v1 in the namespace from day one — supports versioning when you break things later
  • Return WP_REST_Response or WP_Error from callbacks, never raw arrays

JSON Schema validation

WordPress REST API uses JSON Schema for argument validation. Declare the shape and let WordPress reject malformed requests automatically.

  • Each args entry can declare type, required, default, enum, format
  • sanitize_callback runs after validation — use sanitize_text_field, absint, etc.
  • validate_callback for custom validation logic beyond schema
  • Schema-validated args automatically generate OpenAPI documentation via the WordPress REST API schema endpoint

Authentication patterns

WordPress REST API supports four authentication mechanisms. Each fits different client types.

Default for same-origin JavaScript clients (your own admin/frontend pages). Uses the WordPress login cookie + nonce. Pass X-WP-Nonce header with the value from wpApiSettings.nonce. Best for in-WordPress dashboards and Gutenberg blocks.

Application passwords

Built into WordPress core since 5.6. User generates an app password from their profile. Client sends Authorization: Basic base64(username:app_password). Best for integrations and automation scripts. Each app password is revocable independently.

JWT

Plugin-based (JWT Authentication for WP-API or similar). Client logs in once, receives a token, sends it as Authorization: Bearer <token> for subsequent requests. Best for headless React/Next.js frontends and mobile apps. Set sensible expiry (1-24h) + refresh tokens.

OAuth 1.0a / OAuth 2.0

For third-party integrations needing delegated access. OAuth 1.0a is the WP REST default (clunky); OAuth 2.0 via plugins (WP OAuth Server, OAuth2 Provider) is friendlier. Best when third parties need access without the user sharing their password.

Permission callbacks done right

permission_callback is your authorization gate. Skipping it (or returning __return_true) is how endpoints get CVEs.

  • Always set permission_callback — never __return_true in production
  • Use current_user_can('capability') for capability checks — edit_posts, manage_options, custom capabilities
  • For object-level permissions, accept $request and check the specific resource
  • Return WP_Error('rest_forbidden', __('Sorry...'), ['status' => 403]) on denial — formats correctly for clients
  • For public read endpoints, permission_callback can return true — but document it explicitly

The most common REST API CVE: Plugins ship with permission_callback => __return_true as a “todo” they forgot. The endpoint exposes admin-level data to anonymous users. WPScan flags this regularly. Always check permission_callback in code review before shipping.

Error handling and response formatting

Consistent errors make integrators happy. WordPress REST API has conventions worth following.

  • Return WP_Error('error_code', __('Human message'), ['status' => 400]) — WordPress formats it correctly
  • Use HTTP status codes appropriately — 400 for validation, 401 for auth, 403 for permission, 404 for missing, 500 for server
  • Custom error codes follow my_plugin_invalid_input namespacing convention
  • Include actionable messages — “Field ’email’ must be a valid email address” beats “Invalid input”
  • For success, return new WP_REST_Response($data, 200) — explicit beats implicit

Pagination and filtering

List endpoints need pagination. Follow WordPress conventions:

  • Accept page and per_page query parameters
  • Set sensible defaults — page=1, per_page=10
  • Cap per_page at a max (100 typically) to prevent abuse
  • Set response headers — X-WP-Total (total count), X-WP-TotalPages (total pages)
  • For filtering, accept query params matching schema — ?status=active&owner=42

Headless WordPress edge cases

When the WordPress REST API powers a Next.js / Nuxt / Remix frontend, watch for:

  • CORS — set Access-Control-Allow-Origin headers via rest_pre_serve_request filter for the headless frontend domain
  • Caching — REST API responses are uncached by default; add edge cache (Cloudflare, Varnish) for read-heavy endpoints
  • Preview — preview tokens or Gatsby preview for staged content
  • Image URLs — featured_media returns ID, not URL; either expand via _embed or fetch separately
  • Nested data?_embed includes embedded resources; saves round-trips

Versioning your custom REST API

When you need to break a contract, versioning saves you. The two patterns:

  • Namespace-versionedmy-plugin/v1, my-plugin/v2. Old version stays alive until deprecation. Most common pattern
  • Header-versionedAccept: application/vnd.my-plugin.v1+json. Cleaner URLs but harder for integrators

Authentication — FAQs

Which WordPress REST API authentication should I use?

For same-origin JavaScript (your admin pages or Gutenberg blocks): cookie + nonce. For automation/integrations: application passwords (built-in to WordPress 5.6+). For headless React/Next.js frontends: JWT (via plugin) with sensible expiry. For third-party integrations: OAuth 2.0. Cookie auth is simplest; JWT is most flexible for SPA architectures.

Are application passwords secure enough for production?

Yes, when used over HTTPS only. Each app password is revocable independently from the user’s main password, has a label for auditing, and uses Basic auth which most HTTP libraries support natively. Application passwords are the right default for automation and integration use cases.

How do I handle CORS for headless WordPress?

Hook into rest_pre_serve_request filter and send appropriate Access-Control-Allow-Origin headers for your frontend domain (not * in production). Also handle OPTIONS preflight requests by returning the right headers. Many plugins (WP CORS, JWT plugins) ship CORS handling — useful as starting points but verify the rules fit your security model.

Performance — FAQs

How fast is the WordPress REST API?

Out of the box: 200-600ms TTFB on shared hosting, 80-200ms on managed WordPress. The bottleneck is WordPress core load + plugin load on every request. Optimizations: object cache (Redis), edge cache (Cloudflare APO), batch endpoints to reduce round-trips, and ?_fields to limit response size.

Can I cache REST API responses?

Yes — for read endpoints. Use Cloudflare APO or a custom rest_pre_dispatch filter that returns cached responses. Cache invalidation needs hooks on save_post, comment_post, etc. For authenticated endpoints, cache per-user (or skip cache). Most full-site cache plugins ignore REST API by default — configure them explicitly.

Should I expose every plugin feature via REST?

No — only what genuinely needs programmatic access. Each REST endpoint is a security surface (auth + validation + rate limit). Internal admin operations (settings forms, bulk actions) are often better served by admin-ajax or form posts. REST is for: data needed by JavaScript clients, integrations, headless frontends, and mobile apps.

Need a custom plugin with a clean REST API and proper auth?

A WordPress REST API done right means proper namespaces, capability-based permission callbacks, JSON-schema validation, rate limiting, and never the dreaded __return_true on permissions. I build plugins with REST APIs that headless frontends and external integrations can rely on — secure by default, documented, and versioned for production use.

See my WordPress plugin development service

Leave a Reply