Category: WordPress Development
The WordPress REST API is one of the most useful tools for developers who want to connect WordPress with JavaScript apps, mobile apps, dashboards, automation tools, external services, headless frontends, and custom plugin workflows.
Instead of treating WordPress only as a PHP-rendered CMS, the REST API lets you work with WordPress data through structured JSON endpoints. You can read posts, create pages, update users, upload media, manage custom data, and expose your own plugin features through custom routes.
This guide explains the WordPress REST API from a practical developer angle: how routes work, how authentication works, how to fetch and update data, how to register custom endpoints, how to secure those endpoints, and what mistakes to avoid in production.
TL;DR: What Developers Should Know
The WordPress REST API lets developers interact with WordPress using JSON over HTTP. Use it when you need a headless frontend, custom admin interface, mobile app, external integration, plugin endpoint, or JavaScript-driven feature. Public content can usually be read without authentication, but creating, editing, deleting, or accessing private data requires authentication and permissions. Use Application Passwords for server-to-server scripts, cookie/nonces for logged-in browser requests, and permission_callback for every custom endpoint. Always validate inputs, sanitize outputs, check capabilities, paginate large responses, and avoid exposing sensitive data.
What Is the WordPress REST API?
The WordPress REST API is a set of URLs that allow applications to send and receive WordPress data as JSON. A developer can use these endpoints to retrieve posts, create content, upload media, update settings, manage users, or build custom workflows.
The default API root usually looks like this:
https://example.com/wp-json/
A posts endpoint usually looks like this:
https://example.com/wp-json/wp/v2/posts
In plain English, the REST API turns WordPress data into structured resources that another app can request, filter, create, update, or delete depending on authentication and permissions.
Common developer use cases:
- Building a React, Vue, Next.js, or Nuxt frontend powered by WordPress content.
- Creating a custom dashboard for editors, clients, or internal teams.
- Connecting WordPress to a CRM, ERP, app, or automation workflow.
- Publishing posts from an external tool.
- Uploading media programmatically.
- Creating custom plugin endpoints.
- Replacing slow or messy
admin-ajax.phpworkflows with cleaner API routes. - Building mobile apps that read or write WordPress content.
Official reference: WordPress REST API Handbook.
REST API Basics: Routes, Endpoints, and Methods
In WordPress REST API terminology, a route is the URL path and an endpoint is the callback behavior attached to that route. A route can support one or more HTTP methods.
Common HTTP methods:
| Method | Purpose | WordPress Example |
|---|---|---|
GET |
Read data | List posts or fetch one page |
POST |
Create data | Create a new post |
PUT / PATCH |
Update data | Update an existing post |
DELETE |
Delete data | Delete a post, user, or custom item |
Default examples:
# List posts
GET /wp-json/wp/v2/posts
# Get one post
GET /wp-json/wp/v2/posts/123
# List pages
GET /wp-json/wp/v2/pages
# List categories
GET /wp-json/wp/v2/categories
# List media
GET /wp-json/wp/v2/media
Official reference: REST API Posts Reference.
How to Fetch Posts With JavaScript
A simple frontend request can fetch public posts from WordPress without authentication.
fetch('https://example.com/wp-json/wp/v2/posts')
.then((response) => response.json())
.then((posts) => {
console.log(posts);
})
.catch((error) => {
console.error('REST API error:', error);
});
You can limit fields to reduce response size:
fetch('https://example.com/wp-json/wp/v2/posts?_fields=id,date,slug,title,excerpt,link')
.then((response) => response.json())
.then((posts) => console.log(posts));
For production, avoid pulling unnecessary fields. Large REST responses can slow dashboards, headless frontends, and mobile apps.
Pagination: Do Not Pull Everything at Once
WordPress REST API collection endpoints are paginated. This matters when a site has hundreds or thousands of posts, products, users, or custom records.
Pagination examples:
# First 10 posts
GET /wp-json/wp/v2/posts
# Page 2
GET /wp-json/wp/v2/posts?page=2
# 50 posts per request
GET /wp-json/wp/v2/posts?per_page=50
# Maximum common per_page value
GET /wp-json/wp/v2/posts?per_page=100
A good app should loop through pages instead of requesting everything in one huge call.
JavaScript pagination example:
async function getAllPosts() {
let page = 1;
let allPosts = [];
let totalPages = 1;
do {
const response = await fetch(
`https://example.com/wp-json/wp/v2/posts?per_page=100&page=${page}`
);
if (!response.ok) {
throw new Error(`Request failed: ${response.status}`);
}
totalPages = Number(response.headers.get('X-WP-TotalPages')) || 1;
const posts = await response.json();
allPosts = allPosts.concat(posts);
page++;
} while (page <= totalPages);
return allPosts;
}
Official reference: REST API Pagination.
Use _fields to Make Responses Smaller
The _fields parameter helps reduce response size by returning only the fields your app actually needs.
GET /wp-json/wp/v2/posts?_fields=id,slug,title,link
This is useful for:
- Mobile apps.
- Headless frontend lists.
- Custom dashboards.
- Admin tools that only need IDs and titles.
- Performance-sensitive API calls.
You can also use _embed when you need related resources, such as featured media, but do not use it blindly because embedded responses can become heavier.
GET /wp-json/wp/v2/posts?_embed
Official reference: REST API Global Parameters.
Authentication: Public vs Protected Requests
Many public REST API requests do not require authentication. For example, public posts and pages can usually be read through the API.
But authenticated requests are required when you want to:
- Create posts.
- Update posts.
- Delete content.
- Upload media.
- Access private content.
- Read protected metadata.
- Manage users.
- Run custom endpoints that require permissions.
Common authentication methods:
| Method | Best For | Notes |
|---|---|---|
| Cookie + nonce | Logged-in browser/admin requests | Good for plugin/admin JavaScript inside WordPress |
| Application Passwords | Server-to-server scripts and integrations | Built into WordPress and revocable per integration |
| OAuth/JWT/plugin auth | Specific apps or external auth systems | Usually plugin or custom implementation |
Official reference: REST API Authentication.
Application Passwords: Practical Authentication for Scripts
Application Passwords are useful when an external script, app, or integration needs authenticated access to WordPress without using the user’s main login password.
Create an Application Password:
- Log in to WordPress.
- Go to Users → Profile.
- Find the Application Passwords section.
- Add a descriptive name, such as
Deployment Script. - Generate the password and copy it immediately.
- Store it securely because WordPress shows it only once.
cURL example:
curl --user "username:application-password" \
https://example.com/wp-json/wp/v2/users/me
Create a draft post with cURL:
curl --user "username:application-password" \
-X POST https://example.com/wp-json/wp/v2/posts \
-H "Content-Type: application/json" \
-d '{
"title": "API Draft Post",
"content": "Created through the WordPress REST API.",
"status": "draft"
}'
Security rules:
- Use HTTPS only.
- Give the API user the minimum role required.
- Use a unique Application Password per integration.
- Revoke unused credentials.
- Never expose Application Passwords in frontend JavaScript.
- Never commit credentials into Git.
Official reference: Application Passwords.
Cookie and Nonce Authentication for Plugin JavaScript
If you are building a plugin or admin screen that runs JavaScript inside logged-in WordPress, cookie authentication with REST nonces is usually the right approach.
PHP: Pass REST data to JavaScript
wp_enqueue_script(
'my-plugin-admin',
plugin_dir_url( __FILE__ ) . 'admin.js',
array( 'wp-api-fetch' ),
'1.0.0',
true
);
wp_localize_script(
'my-plugin-admin',
'MyPluginSettings',
array(
'root' => esc_url_raw( rest_url() ),
'nonce' => wp_create_nonce( 'wp_rest' ),
)
);
JavaScript: Send nonce with request
fetch(`${MyPluginSettings.root}my-plugin/v1/report`, {
method: 'GET',
headers: {
'X-WP-Nonce': MyPluginSettings.nonce
}
})
.then((response) => response.json())
.then((data) => console.log(data));
This is suitable for admin tools, custom dashboards, block editor extensions, and logged-in plugin screens. It is not the right method for external server-to-server integrations.
How to Register a Custom REST API Endpoint
Use register_rest_route() inside the rest_api_init hook. Every custom route should have a namespace, route path, method, callback, and permission callback.
Basic public endpoint:
add_action( 'rest_api_init', function () {
register_rest_route( 'fyrepress/v1', '/status', array(
'methods' => 'GET',
'callback' => 'fyrepress_get_status',
'permission_callback' => '__return_true',
) );
} );
function fyrepress_get_status() {
return rest_ensure_response( array(
'status' => 'ok',
'version' => '1.0.0',
) );
}
Public endpoints should only expose data that is safe for visitors, bots, and external tools to read.
Official references: Adding Custom Endpoints and register_rest_route().
Secure Custom Endpoint With Permissions
A protected endpoint should check the current user’s capability before returning sensitive data or making changes.
Admin-only endpoint:
add_action( 'rest_api_init', function () {
register_rest_route( 'fyrepress/v1', '/settings', array(
'methods' => 'GET',
'callback' => 'fyrepress_get_settings',
'permission_callback' => function () {
return current_user_can( 'manage_options' );
},
) );
} );
function fyrepress_get_settings() {
return rest_ensure_response( array(
'cache_enabled' => true,
'debug_mode' => false,
) );
}
Good permission checks:
current_user_can( 'manage_options' )for admin settings.current_user_can( 'edit_posts' )for editor-level content tools.current_user_can( 'edit_post', $post_id )for post-specific editing.current_user_can( 'read_private_posts' )for private content access.
Do not use __return_true for endpoints that expose private data, user data, configuration values, logs, tokens, customer data, order data, form entries, or admin-only actions.
Validate and Sanitize Request Arguments
A REST API endpoint should not trust input just because it comes from your own app. Validate arguments, sanitize values, and return useful errors.
Endpoint with arguments:
add_action( 'rest_api_init', function () {
register_rest_route( 'fyrepress/v1', '/post-summary/(?P<id>\d+)', array(
'methods' => 'GET',
'callback' => 'fyrepress_get_post_summary',
'permission_callback' => '__return_true',
'args' => array(
'id' => array(
'required' => true,
'validate_callback' => function ( $param ) {
return is_numeric( $param ) && (int) $param > 0;
},
'sanitize_callback' => 'absint',
),
),
) );
} );
function fyrepress_get_post_summary( WP_REST_Request $request ) {
$post_id = absint( $request['id'] );
$post = get_post( $post_id );
if ( ! $post || 'publish' !== $post->post_status ) {
return new WP_Error(
'fyrepress_not_found',
'Post not found.',
array( 'status' => 404 )
);
}
return rest_ensure_response( array(
'id' => $post_id,
'title' => get_the_title( $post_id ),
'excerpt' => wp_strip_all_tags( get_the_excerpt( $post_id ) ),
'link' => get_permalink( $post_id ),
) );
}
Validation checklist:
- Use
absint()for IDs. - Use
sanitize_text_field()for plain text. - Use
sanitize_email()for emails. - Use
esc_url_raw()for stored URLs. - Use capability checks before returning private data.
- Return
WP_Errorwith useful HTTP status codes.
Return Proper REST Responses and Errors
A good API should be predictable. Do not return random strings, raw PHP errors, or inconsistent response formats.
Success response:
return rest_ensure_response( array(
'success' => true,
'data' => array(
'message' => 'Settings saved.',
),
) );
Error response:
return new WP_Error(
'fyrepress_invalid_request',
'The request is missing a valid post ID.',
array( 'status' => 400 )
);
Common HTTP status codes:
| Status | Meaning | Use Case |
|---|---|---|
| 200 | OK | Successful read or update |
| 201 | Created | New resource created |
| 400 | Bad Request | Invalid or missing input |
| 401 | Unauthorized | Authentication required |
| 403 | Forbidden | User is authenticated but lacks permission |
| 404 | Not Found | Resource does not exist |
| 500 | Server Error | Unexpected server-side failure |
Add Custom Fields to Existing REST Responses
Sometimes you do not need a full custom endpoint. You only need to add a field to an existing REST API response.
Add reading time to posts:
add_action( 'rest_api_init', function () {
register_rest_field(
'post',
'reading_time',
array(
'get_callback' => function ( $post_arr ) {
$content = get_post_field( 'post_content', $post_arr['id'] );
$words = str_word_count( wp_strip_all_tags( $content ) );
return max( 1, ceil( $words / 200 ) );
},
'schema' => array(
'description' => 'Estimated reading time in minutes.',
'type' => 'integer',
'context' => array( 'view', 'edit' ),
),
)
);
} );
This can be useful for headless blogs, custom dashboards, editorial tools, and frontend apps that need extra metadata.
Expose Custom Post Types in the REST API
If your plugin or theme registers a custom post type, it will not always appear in the REST API unless you enable REST support.
Custom post type example:
register_post_type( 'project', array(
'label' => 'Projects',
'public' => true,
'show_in_rest' => true,
'supports' => array( 'title', 'editor', 'thumbnail', 'excerpt' ),
) );
This creates a REST route similar to:
/wp-json/wp/v2/project
For custom taxonomies, also use show_in_rest if the taxonomy should be available to the block editor or external API consumers.
register_taxonomy( 'project_type', 'project', array(
'label' => 'Project Types',
'public' => true,
'show_in_rest' => true,
) );
REST API vs admin-ajax.php
WordPress developers often ask whether they should use the REST API or admin-ajax.php. For modern structured data workflows, the REST API is usually cleaner.
| Feature | REST API | admin-ajax.php |
|---|---|---|
| Data format | Structured JSON | Whatever the callback returns |
| Routes | Clean endpoint URLs | Single Ajax endpoint with action names |
| HTTP methods | GET, POST, PUT/PATCH, DELETE | Usually POST or GET with action |
| Permissions | Built around permission callbacks | Manual checks in callbacks |
| Best for | Modern apps, plugins, integrations | Legacy admin Ajax actions |
Use the REST API for new structured endpoints. Use admin-ajax.php only when legacy compatibility or plugin context makes it necessary.
Security Checklist for WordPress REST API
REST API security mistakes can expose private data or create dangerous write endpoints. Every custom endpoint should be designed as if someone outside your site will try to call it.
Security checklist:
- Always add
permission_callback. - Use
__return_trueonly for intentionally public data. - Check capabilities with
current_user_can(). - Validate and sanitize every request parameter.
- Do not expose API keys, access tokens, logs, server paths, or private options.
- Do not return full user objects unless necessary.
- Do not expose customer, order, form, membership, or LMS data without strict permission checks.
- Rate-limit expensive endpoints if needed.
- Use HTTPS for authenticated requests.
- Revoke Application Passwords that are no longer used.
- Log suspicious API errors carefully without storing secrets.
Performance Checklist for REST API Endpoints
REST API endpoints can become slow if they run heavy queries, return huge responses, or call external services on every request.
Performance checklist:
- Paginate large collections.
- Use
_fieldsto reduce response size. - Cache expensive computed data.
- Avoid unbounded
WP_Querycalls. - Do not run slow external API calls during every request.
- Use object cache for repeated queries.
- Return IDs and summaries when full objects are not needed.
- Avoid
meta_queryabuse on large databases. - Benchmark custom endpoints before exposing them publicly.
- Use server logs or monitoring to detect heavy API traffic.
FyrePress tool: If REST requests are causing slow logs or server errors, review the log excerpts with the FyrePress Server Log Analyzer.
Common REST API Problems and Fixes
Problem: 404 on /wp-json/
Check permalinks, rewrite rules, security plugins, server config, and whether REST routes are blocked.
Problem: 401 Unauthorized
Authentication is missing or invalid. Check Application Password format, HTTPS, user credentials, and authorization headers.
Problem: 403 Forbidden
The user is authenticated but does not have the required capability. Review your permission_callback.
Problem: CORS errors
The browser is blocking cross-origin requests. Review allowed origins carefully. Do not blindly allow every origin for authenticated endpoints.
Problem: Slow API responses
Check database queries, response size, external API calls, cache, and whether the endpoint is returning too much data.
Problem: Custom endpoint works for admins but not visitors
Review permissions. If the endpoint is intended to be public, use a safe public response and permission_callback => '__return_true'. If it is protected, use authentication and capabilities.
Problem: REST API blocked by security plugin
Check security plugin firewall rules. Some plugins restrict REST API access aggressively, which can break the block editor, apps, and integrations.
Debugging REST API Requests
Debug REST API issues from multiple angles: browser, server, WordPress logs, and API client.
Useful debugging tools:
- Browser DevTools Network tab.
- Postman or Insomnia.
- cURL from terminal.
- WordPress debug log.
- Server error logs.
- Query Monitor.
- REST route index at
/wp-json/.
Enable debug logging:
define( 'WP_DEBUG', true );
define( 'WP_DEBUG_LOG', true );
define( 'WP_DEBUG_DISPLAY', false );
Test with cURL:
curl -i https://example.com/wp-json/wp/v2/posts?per_page=1
Look at status code, response headers, body, and error messages. A clear WP_Error response is much easier to debug than a silent failure.
Best REST API Use Cases by Developer Type
For plugin developers
Use the REST API for custom settings screens, background reports, plugin dashboards, integrations, and replacing messy Ajax handlers with structured endpoints.
For theme developers
Use the REST API for dynamic blocks, search interfaces, filters, frontend content loading, and editor-side tools.
For headless developers
Use REST endpoints to fetch posts, pages, media, menus, taxonomies, and custom post types. Be strict with caching, pagination, and response size.
For WooCommerce developers
Use WooCommerce’s own REST APIs for store data, orders, products, and customer workflows. Be extra careful with authentication, rate limits, and private order/customer data.
For agency developers
Use the REST API for maintenance scripts, site audits, dashboard reporting, content sync, and client portals. Use Application Passwords per client/integration and revoke credentials when projects end.
REST API Production Checklist
Before shipping a REST API feature, use this checklist.
- Every custom route has a namespace.
- Every custom route uses
permission_callback. - Public endpoints expose only safe public data.
- Protected endpoints check capabilities.
- All input is validated and sanitized.
- Responses are consistent and documented.
- Errors return useful HTTP status codes.
- Large responses are paginated.
- Heavy queries are cached or optimized.
- External API calls have timeouts.
- Secrets are never returned to the browser.
- Application Passwords are stored securely and revocable.
- Endpoints are tested with authenticated and unauthenticated users.
- REST behavior is tested with security/cache plugins enabled.
- Logs are monitored after deployment.
Final Recommendation
The WordPress REST API is one of the best tools for modern WordPress development. Use it when you need structured data access, custom plugin endpoints, headless content delivery, JavaScript-driven interfaces, mobile apps, or external integrations.
The most important rule is security first. Public endpoints should expose only safe public data. Protected endpoints should use proper authentication, capability checks, validation, and sanitization. Application Passwords are useful for scripts and integrations, while cookie and nonce authentication is better for logged-in browser requests inside WordPress.
For performance, keep responses small, paginate collections, cache expensive data, and avoid heavy queries. A clean REST API endpoint should be predictable, permission-aware, easy to debug, and safe to use in production.
Frequently Asked Questions
What is the WordPress REST API used for?
The WordPress REST API is used to read, create, update, and delete WordPress data through JSON endpoints. Developers use it for headless sites, mobile apps, plugin dashboards, integrations, custom endpoints, and automation.
Is the WordPress REST API public?
Public WordPress content is often accessible through the REST API without authentication. Private content, protected data, user data, settings, and write actions require authentication and proper permissions.
How do I access WordPress REST API posts?
You can access public posts with a request such as https://example.com/wp-json/wp/v2/posts. Use parameters like page, per_page, search, categories, and _fields to control the response.
How do I authenticate with the WordPress REST API?
Use cookie and nonce authentication for logged-in browser requests inside WordPress. Use Application Passwords for server-to-server scripts or external tools. Other methods, such as OAuth or JWT, usually require plugins or custom implementation.
Are WordPress Application Passwords safe?
Application Passwords are useful when handled properly. Use HTTPS, create one password per integration, give the user only the permissions required, store credentials securely, and revoke unused passwords.
How do I create a custom WordPress REST API endpoint?
Register a route inside the rest_api_init hook with register_rest_route(). Add a namespace, route path, HTTP method, callback, permission callback, and argument validation.
What is permission_callback in WordPress REST API?
permission_callback controls whether the current request is allowed to access an endpoint. Public endpoints can use __return_true, but protected endpoints should check capabilities with current_user_can().
Can I disable the WordPress REST API?
You can restrict REST API access, but fully disabling it can break the block editor, plugins, themes, mobile apps, and integrations. Restrict sensitive endpoints instead of blocking the entire API blindly.
Is REST API better than admin-ajax.php?
For modern structured data workflows, the REST API is usually better because it uses clean routes, HTTP methods, JSON responses, and permission callbacks. admin-ajax.php is still used for some legacy Ajax workflows.
How do I make the WordPress REST API faster?
Use pagination, limit response fields with _fields, cache expensive results, avoid heavy meta queries, reduce external API calls, and optimize database queries inside custom endpoint callbacks.